Иллюстрация к статье «Что такое нейросеть: от токенов и весов до GPU и CUDA»
Источник ↗

Введение

Cтатья поднимается на уровень архитектуры. Мы разберём типовую нейросеть, отдельные слои и современный трансформер: attention, multi-head attention, feed-forward блок, residual-связи, нормализацию, logits и выходную голову. После этого станет понятнее, почему только линейных преобразований недостаточно и зачем в модели нужны ReLU, GELU, sigmoid и другие нелинейности.

Последняя часть посвящена вычислительному железу. CPU и GPU сравниваются не на уровне лозунга «у GPU больше ядер», а через устройство ядер, потоков, warps, SM, регистров, shared memory, VRAM, kernel launch и библиотечного стека. Цель статьи - дать читателю цельную картину: от символов в тексте до матричных операций, от матричных операций до GPU-ядер, от GPU-ядер до библиотек PyTorch, CUDA, cuBLAS, cuDNN, NCCL, TensorRT, vLLM, llama.cpp и GGUF.

1. Что такое данные для модели

Для человека текст является словами и смыслом, изображение является объектами и сценой, а звук является речью или сигналом. Для компьютера всё это сначала должно стать числовой структурой. Текст превращается в последовательность токенов, изображение - в массив пикселей или патчей, звук - в отсчёты или спектральные признаки, а табличная задача - в набор числовых колонок. Модель не получает «кот», «дрон» или «антенна» как готовое понятие; она получает числовое представление, которое дальше обрабатывается слоями.

Это не означает, что нейросеть работает «вслепую». Во время обучения она подбирает такие внутренние числовые представления, которые помогают уменьшать ошибку на задаче. Если задача языковая, полезными становятся признаки, связанные с грамматикой, контекстом, тематикой и вероятным продолжением текста. Если задача визуальная, полезными становятся признаки краёв, текстур, форм и частей объектов. Если задача радиотехническая, полезными могут быть признаки частоты, мощности, формы спектра, ширины полосы и временной структуры сигнала.

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

2. Tokenizer и словарь: кто делает vocabulary и почему он не единый

Языковая модель не начинает работу со слов в обычном человеческом смысле. Перед ней стоит tokenizer - программа, которая разбивает строку на маленькие фрагменты и заменяет каждый фрагмент на ID, то есть номер в словаре. Токеном может быть целое слово, часть слова, знак препинания, пробел, кусок кода, байт или специальный управляющий символ. Поэтому фраза «нейросеть работает» может превратиться не в два слова, а в несколько токенов, например «нейро», «сеть» и « работает», если именно так устроен tokenizer конкретной модели.

Словарь модели не является единым мировым стандартом. Его создают разработчики модели на этапе подготовки tokenizer, обычно до основного обучения LLM. Большие компании и лаборатории берут огромный корпус текстов, очищают его от мусора, приводят к нужному формату, добавляют разные языки, код, документы, диалоги и служебные шаблоны, а затем запускают алгоритм построения словаря. В открытых и коммерческих моделях чаще встречаются BPE, byte-level BPE, SentencePiece/Unigram и похожие методы. Их задача - найти такие фрагменты текста, которые часто встречаются и дают хороший компромисс между длиной последовательности и размером словаря.

Например, OpenAI публично предоставляет библиотеку tiktoken, которая описана как быстрый BPE-tokenizer для моделей OpenAI. У DeepSeek в открытых конфигурациях можно увидеть фиксированный vocab_size, например 129280 токенов для семейства DeepSeek-V3. Это не значит, что инженеры вручную выписали 129280 слов. Алгоритм построил набор субсловных единиц и специальных токенов на основе данных, а затем этот словарь стал частью модели. Если заменить tokenizer после обучения, модель обычно сломается, потому что embedding-таблица и веса обучались именно под этот набор ID.

Опорная формулировка из документации OpenAI Cookbook: GPT-модели видят текст в форме токенов, а tokenizer разбивает строку на список таких токенов. В статье SentencePiece tokenizer описывается как языконезависимый пред и постпроцессор для нейросетевых текстовых систем.

Рисунок 1 - Как обучается tokenizer и как появляется vocabulary модели.
Рисунок 1 - Как обучается tokenizer и как появляется vocabulary модели. Источник ↗

Компромисс словаря особенно важен для русского языка. Если tokenizer хорошо покрывает русский корпус, слово или технический термин часто распадается на меньшее число токенов. Если покрытие плохое, русская фраза дробится на множество мелких частей, занимает больше контекста и становится дороже для inference. Поэтому при выборе LLM важно смотреть не только на число параметров, но и на tokenizer, chat template и качество работы на целевом языке.

3. Embedding: как ID превращается в вектор

ID токена сам по себе почти бесполезен. Если токену «нейро» соответствует номер 15432, это просто индекс, как номер строки в таблице. Поэтому внутри модели есть embedding-таблица: большая обучаемая матрица размера vocab_size x hidden_size. Каждая строка этой таблицы соответствует одному токену из словаря, а каждый столбец является одной координатой скрытого пространства модели.

Когда tokenizer выдаёт последовательность ID, модель берёт соответствующие строки из embedding-таблицы. Если hidden size равен 4096, то каждый токен получает вектор из 4096 чисел. Это не словарное определение и не набор ручных признаков. Нельзя сказать, что координата 17 всегда означает «существительное», а координата 812 всегда означает «технический термин». Смысл распределён по множеству координат и появляется в процессе обучения, потому что именно такие векторы помогают модели предсказывать следующие токены.

Важно отличать начальный embedding от последующего hidden state. Один и тот же токен сначала получает один и тот же начальный вектор, но после attention и feed-forward блоков его представление меняется с учётом контекста. Токен «ключ» в фразе «ключ лежит на столе» и в фразе «ключ шифрования утерян» стартует одинаково, но после обработки контекста получает разные hidden-векторы. Поэтому embedding - это стартовая точка, а не финальное понимание токена.

Рисунок 2 - Embedding-таблица: токен ID выбирает строку, а строка становится вектором признаков.
Рисунок 2 - Embedding-таблица: токен ID выбирает строку, а строка становится вектором признаков. Источник ↗

4. Что такое веса, bias и линейный слой

Линейный слой - это базовая вычислительная операция нейросети. Он берёт входной вектор x, умножает его на матрицу весов W и добавляет bias b. Формула записывается как y = Wx + b, а для батча входов обычно используется матричная форма Y = XW + b. Каждый элемент выходного вектора получается как взвешенная сумма входных признаков. Поэтому линейный слой можно понимать как обучаемый смеситель признаков.

Веса - это обучаемые коэффициенты. Большой положительный вес усиливает вклад признака, отрицательный вес меняет направление влияния, а вес около нуля почти отключает связь. В большой модели таких коэффициентов миллиарды. Когда говорят, что модель имеет 7B или 70B параметров, обычно имеют в виду миллиарды обучаемых чисел, большую часть которых составляют матрицы весов в attention, feed-forward и выходных слоях.

Bias, или сдвиг, добавляется после умножения на веса. Если веса задают, как признаки смешиваются и масштабируются, то bias задаёт смещение или порог. Например, нейрон может активироваться только тогда, когда взвешенная сумма входов стала достаточно большой. Отрицательный bias делает срабатывание труднее, положительный - легче. В некоторых современных архитектурах bias в части слоёв убирают, но как базовая идея он важен для понимания линейного преобразования.

5. Почему только линейных слоёв недостаточно

Линейный слой нужен, но одной линейности недостаточно. Причина строгая математически: несколько линейных преобразований подряд без нелинейной активации всегда можно заменить одним линейным преобразованием. Если первый слой считает h = W1x + b1, а второй y = W2h + b2, то после подстановки получается y = (W2W1)x + (W2b1 + b2). Это снова одна формула вида y = W*x + b*. Значит, глубина без нелинейности не создаёт настоящей выразительности.

Практический пример проще увидеть на языке. Фразы «это плохо» и «это не плохо» отличаются одним токеном, но смысл меняется не как простая сумма независимых вкладов. Линейная модель может дать слову «плохо» отрицательный вес, а слову «не» какой-то отдельный вес, но ей трудно выразить правило «если отрицание стоит перед негативным словом, измени значение этого слова». Такое правило зависит от комбинации признаков и их контекста. Именно для таких случаев нужны attention, нелинейные активации и глубина.

Классический математический пример - XOR. Два входных признака принимают значения 0 или 1, а ответ равен 1 только тогда, когда признаки разные. Точки классов нельзя разделить одной прямой линией. Это не проблема плохого обучения, а ограничение линейной формы. Нелинейный слой может создать промежуточные признаки вроде «хотя бы один вход активен» и «оба входа активны», а следующий слой уже комбинирует эти признаки. Так сеть получает способность выражать условия, пороги и взаимодействия признаков.

6. Активации: ReLU, GELU, sigmoid и смысл нелинейности

Активация применяется после линейной части слоя. Сначала считается z = Wx + b, а затем a = f(z). Значение z называют pre-activation, а значение a - activation. Смысл активации не в красивом графике, а в том, что она вводит переключение, изгиб, насыщение или мягкое подавление сигнала. Благодаря этому следующий слой получает не просто линейную сумму, а уже отфильтрованный признак.

ReLU задаётся формулой f(x) = max(0, x). Она обнуляет отрицательные значения и пропускает положительные. Справа от нуля график выглядит как прямая, но функция всё равно нелинейна, потому что в нуле есть излом. Этот излом делает нейрон похожим на вентиль: для одних входов признак выключен, для других включен. Много таких вентилей создают кусочно-линейную поверхность решения.

GELU работает мягче. Она не отрезает все отрицательные значения резко, а плавно ослабляет сигнал. В трансформерах это полезно, потому что признаки токена редко бывают строго полезными или строго бесполезными. Слабый сигнал может стать важным после следующих слоёв, поэтому жёсткая ReLU иногда слишком груба. GELU часто используется в feed-forward блоках трансформеров именно как мягкая нелинейная фильтрация.

Sigmoid сжимает любое число в диапазон от 0 до 1. Поэтому её удобно использовать там, где нужен выход, похожий на вероятность, например в бинарной классификации. Но в глубоких скрытых слоях sigmoid часто создаёт проблему: на больших положительных и отрицательных входах график почти горизонтален, производная близка к нулю, и градиент при обучении затухает. Поэтому sigmoid полезна в выходных задачах, но внутри современных LLM чаще встречаются GELU, SiLU, SwiGLU и их варианты.

Рисунок 3 - ReLU, GELU и sigmoid. ReLU выглядит линейной справа, но нелинейность находится в изломе у нуля.
Рисунок 3 - ReLU, GELU и sigmoid. ReLU выглядит линейной справа, но нелинейность находится в изломе у нуля. Источник ↗

7. Типовая нейросеть: какие слои бывают

Типовая нейросеть состоит из входного преобразования, повторяющихся блоков обработки и выходной головы. Входное преобразование переводит данные в числовые векторы: текст - в токены и embeddings, изображение - в пиксели или patches, сигнал - в отсчёты или спектрограмму. Блоки обработки меняют эти представления через линейные слои, активации, свёртки, attention, нормализацию, residual-связи и иногда dropout. Выходная голова переводит внутренние признаки в формат ответа.

Свёрточные слои часто используются для изображений и сигналов. Они применяют небольшой фильтр к локальному окну входа, например 3x3 пикселя, и строят карты признаков. Карта признаков - это двумерная таблица чисел, показывающая, где на изображении обнаружен определённый шаблон: вертикальный край, горизонтальная линия, текстура или более сложный элемент. В ранних слоях карты признаков простые, в глубоких - более абстрактные.

Классификационная голова - это выходной слой для задачи классификации. Если модель должна выбрать один из 1000 классов изображения, голова выдаёт 1000 чисел, которые потом превращаются в вероятности. Если модель решает задачу «спам или не спам», голова выдаёт один или два score. В LLM аналогичную роль выполняет language modeling head: он выдаёт logits по всему словарю следующего токена.

8. Трансформер: полный путь от токенов до logits

Трансформер - это архитектура нейросети для последовательностей. Последовательностью может быть текст, код, аудио, временной ряд или изображение, разбитое на patches. Исходная работа Attention Is All You Need предложила архитектуру, основанную на attention-механизмах без рекуррентных и свёрточных блоков. Современные LLM являются потомками этой идеи, хотя детали сильно изменились.

Цепочка языковой модели выглядит так: текст -> tokenizer -> token IDs -> embeddings -> positional information -> много transformer blocks -> final hidden state -> language modeling head -> logits -> softmax/sampling -> следующий токен. Каждый шаг имеет конкретную функцию. Tokenizer делает текст дискретным, embedding делает ID вектором, позиционная информация добавляет порядок, attention смешивает информацию между токенами, FFN перерабатывает представление каждого токена, а выходная голова переводит hidden-вектор в оценки по словарю.

Позиционная информация нужна потому, что порядок токенов меняет смысл. «Собака укусила человека» и «человек укусил собаку» содержат одинаковые слова, но разные отношения. В современных LLM часто используют rotary position embedding, или RoPE, но для вводного понимания достаточно идеи: к представлению токена добавляется информация о его положении и расстояниях до других токенов.

Рисунок 4 - Один transformer block: attention, residual, normalization и feed-forward.
Рисунок 4 - Один transformer block: attention, residual, normalization и feed-forward. Источник ↗

9. Attention: Query, Key, Value и multi-head attention

Attention нужен, чтобы токены могли учитывать друг друга. Внутри блока каждый hidden-вектор линейно проецируется в три вектора: Query, Key и Value. Query можно понимать как запрос токена, Key - как признаки, по которым другие токены могут быть найдены, а Value - как информацию, которая будет передана при высоком attention-весе. Это не ручные правила, а обучаемые проекции через матрицы весов.

Дальше Query одного токена сравнивается с Key других токенов через скалярные произведения. Чем выше совпадение, тем больше score. Затем softmax превращает scores в нормированные веса внимания. После этого модель берёт Value-векторы других токенов и смешивает их с этими весами. На выходе токен получает новое представление, в котором уже присутствует информация из контекста.

Multi-head attention делает несколько таких сравнений параллельно. Каждая голова имеет свои матрицы Q, K и V, поэтому разные головы могут учиться разным типам связей: локальным зависимостям, дальним ссылкам, структуре кода, согласованию слов, форматированию или другим статистическим паттернам. После вычисления головы объединяются, и результат проходит через выходную линейную проекцию.

Рисунок 5 - Attention через Query, Key, Value. Токен обновляет своё представление через смешивание Value-векторов других токенов.
Рисунок 5 - Attention через Query, Key, Value. Токен обновляет своё представление через смешивание Value-векторов других токенов. Источник ↗

10. Feed-forward block, нормализация и residual-связи

Feed-forward block, или FFN, находится внутри трансформерного блока после attention. Если attention отвечает за обмен информацией между токенами, то FFN обрабатывает каждый токен отдельно. Обычно FFN сначала расширяет размерность hidden-вектора, затем применяет нелинейную активацию, а потом сжимает размерность обратно. Например, hidden size может быть 4096, а intermediate size в FFN - 11008 или больше.

Расширение размерности даёт модели место для вычисления множества промежуточных признаков. Активация вроде GELU или SiLU решает, какие из этих признаков пропустить, ослабить или почти обнулить. Второй линейный слой собирает результат обратно в исходную размерность. Поэтому FFN можно понимать как локальную обработку токена после того, как attention добавил контекст от других токенов.

Нормализация стабилизирует масштаб чисел. В глубоких моделях после десятков слоёв значения могут становиться слишком большими или слишком маленькими, что ухудшает обучение. LayerNorm или RMSNorm приводят распределение hidden-векторов к более управляемому виду. Residual-связь добавляет выход подблока к его входу, например x_new = x + F(x). Это сохраняет старую информацию и облегчает прохождение градиентов через глубокую сеть.

11. Logits, softmax и выходная голова

После последнего transformer block модель получает финальный hidden-вектор для каждого токена. В генерации текста обычно важен последний токен, потому что по нему предсказывается следующий. Этот hidden-вектор передаётся в language modeling head - линейный слой, который выдаёт одно число для каждого токена словаря. Если словарь содержит 100000 токенов, на выходе будет 100000 чисел.

Эти числа называются logits. Logits - это сырые оценки модели, а не вероятности. Они могут быть отрицательными, положительными и не обязаны суммироваться в единицу. Чтобы получить вероятности, применяется softmax: большие logits становятся большими вероятностями, маленькие logits - малыми вероятностями, а сумма всех вероятностей становится равной 1.

После softmax выбирается следующий токен. Если взять самый вероятный токен, получится жадное декодирование. Если использовать temperature, top-k или top-p sampling, модель может выбирать более разнообразные продолжения. Выбранный токен добавляется к контексту, после чего модель снова проходит шаг inference для генерации следующего токена. Поэтому LLM генерирует текст последовательно, токен за токеном.

12. Обучение: loss, градиент, backpropagation и optimizer

Во время обучения модель сначала делает forward pass: получает вход и выдаёт предсказание. Затем считается loss - численная ошибка. Для языковой модели типичная задача - предсказать следующий токен, поэтому loss показывает, насколько сильно распределение вероятностей модели отличается от правильного следующего токена. Если модель уверенно выбрала неправильный токен, loss большой. Если правильный токен получил высокую вероятность, loss меньше.

Backpropagation вычисляет градиенты. Градиент по весу показывает, как небольшое изменение этого веса повлияло бы на loss. Если увеличение веса повышает ошибку, вес нужно уменьшать. Если изменение веса помогает уменьшить ошибку, оптимизатор использует это направление. PyTorch autograd строит вычислительный граф операций и автоматически считает такие градиенты через правило цепочки.

Optimizer обновляет веса по градиентам. Простейший градиентный спуск делает шаг против направления роста ошибки. Adam и AdamW учитывают историю градиентов и масштабируют обновления стабильнее, чем простой шаг. Поэтому обучение нейросети можно описать так: веса хранят настраиваемые преобразования, loss измеряет ошибку, градиент показывает направление исправления, optimizer меняет веса, а много повторений этого цикла постепенно формирует полезную модель.

Рисунок 6 - Цикл обучения: forward pass, loss, backpropagation, optimizer, обновление весов.
Рисунок 6 - Цикл обучения: forward pass, loss, backpropagation, optimizer, обновление весов. Источник ↗

13. Почему всё это ложится на GPU

Основная вычислительная нагрузка нейросетей - это операции над матрицами и тензорами. Линейные слои, attention-проекции, FFN, выходная голова и многие операции обучения сводятся к GEMM, то есть матричному умножению. В таком вычислении каждый элемент результата можно считать через большое число однотипных умножений и сложений. Это плохо подходит для малого числа универсальных ядер CPU, но хорошо подходит для тысяч параллельных потоков GPU.

CPU оптимизирован для низкой задержки и универсальности. У него сравнительно мало сложных ядер, развитые кэши, предсказатель ветвлений, out-of-order execution и сильная работа с нерегулярным кодом. Он отлично управляет операционной системой, файлами, сетевыми запросами, логикой программы и подготовкой данных. Но если задача состоит из огромного числа одинаковых операций над тензорами, большая часть сложности CPU не даёт преимущества.

GPU оптимизирован для throughput, то есть для большого объёма однотипных операций в единицу времени. У него много потоковых мультипроцессоров и простых исполнительных блоков. Они работают эффективно, когда одна и та же инструкция применяется к множеству элементов данных. Именно поэтому обучение и inference LLM обычно выполняют на GPU: веса и активации лежат в VRAM, матрицы режутся на тайлы, а тысячи потоков одновременно считают части результата.

Рисунок 7 - CPU для задач ИИ: немного мощных универсальных ядер, развитые кэши и сложная логика управления.
Рисунок 7 - CPU для задач ИИ: немного мощных универсальных ядер, развитые кэши и сложная логика управления. Источник ↗

14. Как устроено выполнение на GPU: threads, blocks, warps, SM

CUDA-модель описывает выполнение через threads, blocks и grid. Thread - это минимальная программная единица исполнения. Threads объединяются в thread blocks, а blocks образуют grid, который запускается как kernel. Kernel launch - это запуск функции на GPU для большого числа потоков. Например, один kernel может считать матричное умножение, другой - нормализацию, третий - attention softmax, если они не объединены в более сложный fused kernel.

На железе NVIDIA потоки исполняются группами, которые называются warps. Warp обычно содержит 32 потока, которые выполняют одну инструкцию над разными данными. Это модель SIMT: single instruction, multiple threads. Если потоки внутри warp расходятся по разным веткам if/else, возникает warp divergence, и эффективность падает, потому что разные ветки приходится выполнять по очереди. Поэтому GPU любит регулярные вычисления и плохо любит хаотичную логику.

Thread blocks назначаются на streaming multiprocessors, или SM. Внутри SM находятся регистры, shared memory, планировщики warp, обычные CUDA cores и специализированные tensor cores. Tensor cores ускоряют матричные операции в низкой и смешанной точности, например FP16, BF16, FP8 или другие форматы на современных GPU. Именно через такие блоки нейросетевые операции получают огромную скорость по сравнению с CPU.

Рисунок 8 - Как устроен GPU: thread, warp, block, grid, SM и иерархия памяти.
Рисунок 8 - Как устроен GPU: thread, warp, block, grid, SM и иерархия памяти. Источник ↗

15. Память GPU: почему важна не только арифметика

Производительность нейросети ограничивается не только количеством FLOPS. Чтобы выполнить умножение матриц, GPU должен постоянно читать веса, активации и промежуточные данные. Если данные каждый раз брать из глобальной VRAM, вычислительные блоки могут простаивать. Поэтому быстрые kernels стараются разрезать матрицы на блоки, загрузить тайл в shared memory или регистры, переиспользовать его много раз и только затем записать итог обратно.

Иерархия памяти на GPU обычно включает регистры, shared memory или SRAM на кристалле, кэши и глобальную память VRAM. Регистры очень быстрые, но их мало и они принадлежат потоку. Shared memory быстрее глобальной памяти и доступна потокам внутри блока. Глобальная память большая, но доступ к ней дороже. Поэтому coalesced memory access, то есть последовательные и согласованные чтения соседних адресов потоками, критичен для высокой скорости.

В LLM это особенно заметно на длинном контексте. Веса модели занимают VRAM, KV-cache растёт с длиной контекста, attention и FFN постоянно читают и пишут тензоры. FlashAttention и похожие kernels ускоряют attention не потому, что отменяют математику, а потому что уменьшают лишние обращения к глобальной памяти и лучше используют локальную память GPU. Поэтому GPU-инференс - это не только «много ядер», а ещё и правильная организация движения данных.

16. Библиотеки: кто на самом деле запускает вычисления

Пользователь обычно пишет код на PyTorch, Transformers, llama.cpp, Ollama или другом высокоуровневом инструменте. Но реальная работа происходит ниже. PyTorch строит граф операций и вызывает оптимизированные backend-библиотеки. Для NVIDIA это CUDA Runtime, cuBLAS, cuDNN, NCCL, TensorRT, CUTLASS и специализированные kernels. Эти библиотеки знают, как разложить матричную операцию на tiles, как использовать tensor cores, как организовать память и как выполнять операции на нескольких GPU.

cuBLAS отвечает за линейную алгебру и GEMM. cuDNN предоставляет оптимизированные примитивы для глубокого обучения, например свёртки, нормализации, активации и операции для современных сетей. NCCL используется для коллективной коммуникации между GPU, например all-reduce при распределённом обучении. TensorRT и TensorRT-LLM оптимизируют inference, объединяют операции, выбирают kernels и уменьшают задержку. vLLM оптимизирует серверный inference LLM, а llama.cpp и GGUF важны для локального запуска моделей на CPU/GPU в компактных форматах.

Поэтому нельзя говорить, что «Python считает нейросеть». Python чаще управляет графом и передаёт команды. Реальные тензорные операции выполняются C++/CUDA kernels на GPU. Когда пользователь вызывает model(input_ids), внутри проходит длинная цепочка: tokenizer готовит ID, PyTorch создаёт tensors, CUDA запускает kernels, cuBLAS считает матричные умножения, attention kernels обрабатывают контекст, а runtime возвращает logits.

Рисунок 9 - Программный стек ИИ: от железа и драйверов до фреймворков и приложений.
Рисунок 9 - Программный стек ИИ: от железа и драйверов до фреймворков и приложений. Источник ↗

СПАСИБО!!!!

17. Источники и опорные материалы

OpenAI Cookbook. How to count tokens with tiktoken. https://developers.openai.com/cookbook/examples/how_to_count_tokens_with_tiktoken

OpenAI tiktoken GitHub repository. https://github.com/openai/tiktoken

Taku Kudo, John Richardson. SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing. ACL 2018. https://aclanthology.org/D18-2012/

Ashish Vaswani et al. Attention Is All You Need. NeurIPS 2017. https://arxiv.org/abs/1706.03762

DeepSeek-V3 model documentation and configuration. https://huggingface.co/deepseek-ai/DeepSeek-V3 and Transformers documentation for DeepSeek-V3.

NVIDIA CUDA C++ Programming Guide. https://docs.nvidia.com/cuda/cuda-c-programming-guide/

NVIDIA cuBLAS documentation. https://docs.nvidia.com/cuda/cublas/

NVIDIA cuDNN documentation. https://docs.nvidia.com/deeplearning/cudnn/

NVIDIA NCCL documentation. https://docs.nvidia.com/deeplearning/nccl/

PyTorch autograd documentation. https://docs.pytorch.org/docs/stable/autograd.html

PyTorch optimizers documentation. https://docs.pytorch.org/docs/stable/optim.html