Нейромережі простими словами
Привіт!
В інтернеті можна знайти різні пояснення того, як працюють нейромережі, але ті, що мені траплялися, були або надто специфічні й орієнтовані на фахівців, або надто спрощені.
Спробував написати свої пояснення, які були б не надто спрощені, але при цьому за можливості зрозумілі.
Стаття на 10 відсотків скомпільована з інших статей, на 30 відсотків скомпільована з багатьох діалогів з різними LLM і на 60 відсотків “написана від руки” на підставі статей і відповідей.
Цікаві питання та відповіді
Чи можливо в кількох реченнях описати, як працює нейромережа?
Спробую. Уявіть структуру, що складається з сотень тисяч векторів у тисячовимірному просторі. Для кожного слова, ділянки зображення або будь-якої іншої сутності в результаті навчання обчислено свій вектор, у координатах якого закладено взаємозв’язок з усіма іншими векторами простору. Таким чином, для всіх векторів питання можна розрахувати взаємозв’язок з усіма іншими наявними векторами простору. Вектори, що мають найбільший взаємозв’язок, будуть відповіддю.
Чому в LLM моделях на вході дається питання, а на виході виходить відповідь, а не перефразоване питання?
Модель навчена на текстах «питання→відповідь», тому, побачивши питання, далі генеруються не слова самого питання, а найімовірніше продовження — відповідь.
Під час інструкційного навчання (SFT) і RLHF її «заохочують» за корисні відповіді, а не за повтор формулювання, тому параметри зсуваються в бік відповідей.
У термінах імовірностей: для питання ймовірність наступного токена-відповіді вища, ніж токена, що повторює питання, і декодер обирає саме його.
Дані, на яких навчається LLM модель, застарівають, які є можливості для того, щоб модель генерувала актуальні відповіді?
Retrieval-Augmented Generation (RAG):
Модель при запиті шукає в актуальній зовнішній базі (пошук за векторним або текстовим індексом) релевантні документи і використовує їхній контекст при генерації відповіді. Так можна отримувати свіжу інформацію без перетренування основної мережі
Параметр-ефективне донавчання (LoRA, Adapters):
Замість повного перенавчання моделі вбудовують невеликі адаптери або low-rank матриці (LoRA), які навчаються на нових даних. Це дозволяє швидко й недорого «навчити» модель новим фактам або доменам
Про LoRA я зробив окрему статтю:
Цільове редагування ваг (Model Editing):
Алгоритми типу MEMIT/ROME локально коригують ваги моделі для додавання або оновлення конкретних фактів, не зачіпаючи решту знань
Окремі бази знань і графи:
Замість зберігання фактів усередині параметрів LLM виносите їх у зовнішні KB або графи знань, які регулярно оновлюються, а модель лише запитує в них інформацію
Інтеграція з вебпошуком і API:
Підключення до реального часу через веббраузерні плагіни, сторонні API і сервіси пошуку (наприклад, ChatGPT Plugins, Bing Search API) напряму повертає свіжий вміст.
Яким чином LLM модель розуміє різні мови?
Модель “розуміє” різні мови завдяки тому, що в процесі навчання вона бачить тексти багатьма з них і вчиться передбачати наступний фрагмент незалежно від мови. Основні моменти:
Великий багатомовний корпус:
Для навчання збирають тексти (вікіпедія, книги, вебсторінки з Common Crawl та інші) десятками й сотнями мов. Наприклад, у відкритій моделі BLOOM було близько 46 мов, причому частка кожної залежить від обсягу доступних даних
Спільна токенізація subword:
Використовуються алгоритми на кшталт BPE або SentencePiece, які розбивають слова на фрагменти (subword) і включають до словника символи й послідовності з різних абеток. Так модель оперує єдиним набором токенів для всіх мов
Універсальна архітектура трансформера:
У трансформері одні й ті самі ваги беруть участь при обробці будь-яких мов. Тому при навчанні на різних мовах модель знаходить спільні патерни (синтаксис, семантика) і використовує перехресне перенесення знань (cross-lingual transfer)
Не всі мови і не в усіх обсягах:
Навчають лише на тих мовах, де є достатній обсяг текстів. Рідкісні або низькоресурсні мови потрапляють в окремі донавчання або отримують меншу частку даних, тому якість генерації на них нижча
Спеціальні донавчання та адаптери:
Щоб покращити знання маловідомих мов, застосовують донавчання (continual pretraining) на локальних даних або вставляють адаптери (adapters, LoRA), які тонко коригують знання моделі під конкретну мову.
Вхідні дані
На вході нейромережа отримує вхідні дані у вигляді запиту користувача. До запиту користувача також додається загальна інформація, яка дозволяє отримати точнішу відповідь. Якщо нейромережа підтримує RAG (Retrieval-Augmented Generation), то до запиту додаються також:
-
Дані з векторної бази, якщо в неї попередньо додали те, що потрібно для генерації актуальнішої відповіді
-
Витягнуті з інтернет сторінки або документа — для того, щоб скоротити їхній розмір, часто використовується спеціалізована embedding нейромережа, вона знаходить усі відповідні шматки тексту й додає до контексту запиту
Приклад налаштувань embedding нейромережі для Chrome розширення Page Assist

Як працює RAG
Система складається з двох основних компонентів
Retrieval (витягнення):
Шукає релевантну інформацію із зовнішньої бази знань (найчастіше — векторна база, наприклад, FAISS, Qdrant, Weaviate). Ця база зазвичай побудована заздалегідь з текстових документів (pdf, markdown, html та ін.), перетворених на ембединги за допомогою моделі (наприклад, BERT, Instructor, або SentenceTransformer).
Generation (генерація):
LLM (GPT, LLaMA, Mistral та ін.) отримує вихідний запит + знайдені документи і генерує фінальну відповідь.
У деяких нейромережах для відокремлення запиту користувача, контексту, отриманого за допомогою RAG, і загального повідомлення використовується розмітка.
Приклад:
context:
це дані, отримані з інтернет за допомогою embedding нейромережі або з векторного сховища і відсортовані за збігом score у міру спадання
{ "context": [ "Пеніцилін — це перший відкритий антибіотик, вироблений із цвілевого грибка роду Penicillium. Він використовується для лікування бактеріальних інфекцій, таких як ангіна, сифіліс і пневмонія. Пеніцилін руйнує клітинну стінку бактерій, що призводить до їх загибелі.", "Деякі люди мають алергію на пеніцилін. Це може викликати серйозні реакції, включно з анафілаксією, тому перед призначенням препарату важливо уточнити наявність алергії у пацієнта." ], "instructions": "Відповідай простими словами, використовуючи лише інформацію з контексту. Якщо відповіді немає — напиши 'Інформацію не знайдено'.", "question": "Що таке пеніцилін і для чого він застосовується?"}Кроки роботи нейромережі
Крок 1: Токенізація (з тексту / зображення в токени)
Токенізація тексту
LLM не працює зі словами напряму — вона працює з токенами (частинами слів або символами), перетвореними на числа.
Приклад:
Текст користувача:"Привіт, світ"
ID токена у словнику моделі для слова:"Привіт" = 1123"," = 15"світ" = 345
Отримуємо масив ID токенів:["Привіт", ",", "світ"] = [1123, 15, 345]
Як це досягається:
Використовується алгоритм на кшталт Byte Pair Encoding (BPE), Unigram, WordPiece або SentencePiece.
BPE-токенізатор знаходить найчастіші пари символів.
WordPiece будує токени на основі ймовірності ієрархічних розбиттів.
Часто токени — це не окремі слова, а частини слів.
У словник включені спеціальні маркери початку слова (пробіл, “##” тощо), тому жоден субтокен не перетинає межу двох слів.
Токенізація завжди йде "жадібно" — береться максимально довгий токен, що збігається з початком решти рядка.
Приклад:
Текст користувача:"неймовірно"
ID токена у словнику моделі для частин слів:"не" = 24"ймовір" = 126"но" = 36Отримуємо масив ID токенів:["не", "ймовір", "но"] = [24, 126, 36]
Або ще коротший варіант:("не»", "ймо", "##вірно")Різні за змістом слова можуть мати спільні токени, наприклад "привіт" і "приклад" можуть мати спільний токен "при":
Хоча «при» повторюється в обох словах, модель одразу дивиться не тільки на цей шматочок, а й на сусідні токени і на всю фразу (про це далі в статті).
Спочатку «при» перетворюється на вектор — просто набір чисел, що описують цю частину слова.
Потім трансформер (багатошарова мережа) змішує цей вектор з векторами сусідніх токенів («віт» або «клад») і додає інформацію про позицію в реченні.
У підсумку в першому шарі «при» в «привіт» уже відрізняється від «при» в «приклад», тому що туди «підмішався» різний контекст.
Тобто спільний шматочок «при» сам по собі нейтральний, а зміст формується далі по шарах на основі оточення.
Чому токени, а не слова:
Менший словник = економія пам’яті.
Краще обробляються рідкісні й складені слова.
Дозволяє моделі “вчитися” розуміти структуру слів.
Розмір словника, або “vocab_size”, визначає, скільки унікальних токенів може обробляти модель.
Більший словник = менше розбиттів слів на частини, але більший обсяг embedding-шару.
Слово “програмування” може бути цілком одним токеном у моделі з великим словником,
а може розбитися на частини (“пр”, “ограму”, “вання”) у моделі з меншим словником.
Розміри словника популярних моделей, кількість токенів:
Модель | Розмір словника|-----------------------|----------------|LLaMA 1/2 | 32 000 |LLaMA 3 | 128 000 |Gemini 1.0/1.5 | ~4 МБ |Gemma 1/2 | 256 000 |Gemma 3 | 262 000 |Qwen 1.5 | 151 936 |Qwen 2 | 152 064 |Qwen 2.5 | 152 064 |DeepSeek LLM | 102 400 |DeepSeek-Coder | 32 256 |DeepSeek V2/V3 | 102 400 |DeepSeek-R1 | 129 280 |Токенізація зображень
На відміну від текстової токенізації (де токени — це слова, сабворди, символи), у зображеннях токени — це фрагменти зображення або представлення ознак. Нижче розглянуто основні підходи.
Для зображень замість токенів отримують одразу матрицю (або тензор) пікселів.
У класичних згорткових мережах (CNN) маленькі ділянки картинки (наприклад, 3×3 або 5×5 пікселів) ковзають по зображенню, і для кожної ділянки згортка з набором фільтрів видає вектор ознак. Ці вектори збираються в карти ознак і передаються далі.
У сучасних трансформерах для зображень (Vision Transformer) картинку розбивають на «патчі» (квадрати, скажімо, 16×16 пікселів), кожен патч вирівнюють у вектор і теж проєктують через матрицю ембедингів у вектор-представлення, як токен у NLP.
Основні способи токенізації зображень:
Patch Embedding (розбиття на патчі) — класичний підхід ViT:
Зображення ділиться на сітку квадратних патчів, наприклад, 16×16 пікселів.
Кожен патч розгортається у вектор (flatten), потім лінійно проєктується в ембединг фіксованої розмірності (наприклад, 768).
У результаті виходить послідовність токенів: один токен на патч.
Приклад:
224×224 RGB зображення з патчами 16×16 → 14×14 = 196 токенів + [CLS] токен.
Кожен токен: вектор розміром 768.
CNN Feature Maps як токени:
Використовуються згорткові мережі (ResNet, ConvNext) для витягнення ознак.
Просторові ознаки (feature map) на виході можна інтерпретувати як токени, де кожен елемент сітки — вектор.
Застосовується в CLIP та інших гібридних моделях.
VQ-VAE / VQ-GAN токенізація (дискретна):
Кодувальник (енкодер) перетворює зображення на карту ознак і потім квантує її в дискретні токени (індекси зі словника).
Кожен токен — це індекс у словнику візуальних патчів.
Використовується в DALL·E, Imagen, LLaVA та інших мультимодальних генеративних моделях.
Плюси: модель працює зі «словами» візуальної мови.
Мінуси: втрата точності, нестійка генерація.
Segment/Region-based токени (DETR, Region Attention):
Зображення розбивається на змістові регіони (сегментація, об’єкти).
Кожен регіон перетворюється на токен за допомогою агрегації ознак.
Використовується в завданнях детекції об’єктів і візуальної відповіді на питання (VQA).
Patch + Positional Encoding:
Як і в NLP, кожному патчу додається позиційна інформація (абсолютна, відносна або learnable), щоб зберегти просторову структуру зображення.
Максимальна довжина контексту
Контекст — це оперативна пам’ять моделі, розмір контексту це максимальна кількість токенів (слів, символів або їхніх частин), яку мовна модель може обробити за один запит.
Це «обсяг пам’яті», який модель може бачити одночасно, щоб сформувати відповідь. Усе, що виходить за межі цього вікна, модель забуває або не бачить напряму.
Розмір контексту напряму впливає на здатність моделі пам’ятати попередні повідомлення в діалозі.
Модель не має вбудованої довготривалої пам’яті — вона не "пам’ятає" вас як людина. Вона просто обробляє весь попередній діалог як вхідний текст (токени), що передається при кожному запиті. Це й називається контекст.
Максимальна довжина контексту популярних моделей:
| Модель | Максимальна довжина контексту||-------------------------|------------------------------|| LLaMA 1/2 |2 048 / 4 096 || LLaMA 3 |8 192 || Gemma 1/2 |8 192 || Gemma 3 |128 000 || Qwen 1.5 |32 768 || Qwen 2 |32 768 || Qwen 2.5 |131 072 || DeepSeek LLM |4 096 || DeepSeek-Coder |16 384 || DeepSeek V2/V3 |128 000 || DeepSeek-R1 |131 072 || Gemini 1.0/1.5 |32 768 / 1 000 000 |Крок 2: Token embedding (з токена у вектор)
У LLM моделі для кожного ID токена зберігається Token Embedding, або масив цифр. Ці числа описують, що означає цей токен, як якби ти перекладав слово в математичну форму.
Спочатку token embedding має фіксоване значення для кожного токена, але потім уточнюється в міру проходження крізь шари.
Коли Token Embedding проходить через кілька шарів трансформера, він стає контекстуалізованим: він враховує значення всієї фрази. На виході ми отримуємо вектор, який містить “зміст” слова в контексті.
Зв’язки між векторами задаються матрицями ваг між шарами.
Спочатку ці ваги ініціалізуються випадково або за спеціальним правилом (наприклад, Xavier або He), а потім під час навчання для кожної ваги обчислюють градієнт втрати (наскільки помилка зростає або спадає при її зміні).
Алгоритм зворотного поширення (“backpropagation”) розраховує, як зміна кожної ваги вплине на підсумкову помилку, а потім градієнтним спуском (або його варіаціями) коригує ці ваги.
Так поступово посилюються зв’язки між тими компонентами векторів, які разом дають корисну інформацію, і слабшають усі непотрібні — виходить набір взаємопов’язаних векторів, оптимальних для розв’язання завдання.
Розмірність вектора (Embedding Size, d_model) на кожному шарі трансформера
Кількість вимірів у token embedding фіксована для всієї моделі й залежить від її архітектури.
Приклад:
Текст користувача:"Привіт"
ID токена у словнику моделі для слова:"Привіт" = 1123
Token embedding для ID токена 1123 = масив значень у форматі числа з рухомою комою з d_model= 4096 елементів:[0.034, 0.120, 0.905, ..., 0.028]Розмір embedding size популярних моделей:
Модель | d_model |-----------------------|---------|LLaMA 1 (7B) | 4096 |LLaMA 2 (13B) | 5120 |LLaMA 2 (70B) | 8192 |LLaMA 3 (8B) | 4096 |LLaMA 3 (70B) | 8192 |Gemini 1.0 Pro | 6144 |Gemini 1.0 Ultra | 8192 |Gemini 1.5 Pro | 8192 |Gemma 1 (2B) | 2048 |Gemma 1 (7B) | 3072 |Gemma 2 (9B) | 4096 |Gemma 2 (27B) | 6144 |Gemma 3 (12B) | 5120 |Gemma 3 (27B) | 8192 |Qwen 1.5 (7B) | 4096 |Qwen 2 (7B) | 4096 |Qwen 2.5 (7B) | 4096 |DeepSeek LLM (7B) | 4096 |DeepSeek LLM (67B) | 8192 |DeepSeek-Coder (6.7B) | 4096 |DeepSeek-Coder (33B) | 8192 |DeepSeek-R1 | 8192 |Кількість елементів вектора змісту впливає на:
Більша розмірність = більше “місця” для зберігання семантики, синтаксису, контексту.
Це дозволяє розрізняти тонші змісти між токенами.
Розмір пам’яті всіх ваг і активацій зростає квадратично з embedding size.
embedding size = d_model = 4096займає в 4 рази більше пам’яті, ніжembedding size = d_model = 2048Точність вектора
Також числа з плаваючою точкою, з яких складається вектор, можуть мати різну точність:
Менша точність зменшує розмір моделі й пришвидшує обробку.
Зазвичай моделі навчаються на повній точності FP32 (float32), тобто вектор складається з чисел розмірністю 32 біти.
Квантування (Quantization)
для зменшення точності для полегшення моделі використовується квантування — процес перетворення чисел з плаваючою точкою (наприклад, FP32) на компактніші цілочисельні представлення (наприклад, INT8), зі збереженням наближеного значення.
При цьому вводиться невелика втрата точності, але вона часто не впливає критично на якість виводу.
Формат: FP32 (float32), 32 біти = 4 байтиПризначення: Повна точність. Використовується під час навчання моделей, а також при точному інференсі.Забезпечує максимальну точність, але потребує багато пам’яті та обчислювальних ресурсів.Бінарне значення: 01000000 01001001 00001111 11011011Фактичне значення: 3.14159Можливі типи квантування: Не використовується — це повний (не квантований) формат.
Формат: FP16 (float16), 16 біт = 2 байтиПризначення: Половинна точність. Використовується для пришвидшеного навчання та виведення на GPU (наприклад, NVIDIA Tensor Cores).Швидше та вдвічі економніше за пам’яттю порівняно з FP32.Бінарне значення: 01000000 10010000Фактичне значення: ≈ 3.1406Можливі типи квантування: QFloat16 (якщо використовується адаптивне змішування в mixed precision).
Формат: BF16 (bfloat16), 16 біт = 2 байтиПризначення: Альтернатива FP16, що використовується в TPU та деяких GPU. Має ту саму експоненту, що й FP32, але вкорочену мантису.Швидше та компактніше, при цьому зберігає діапазон FP32.Бінарне значення: 01000000 10010000Фактичне значення: ≈ 3.140625Можливі типи квантування: QBFloat16 (рідко використовується напряму, але трапляється в TPU-інференсі).
Формат: INT8 (8 біт), scale = 0.125Призначення: Квантоване ціле число. Використовується в оптимізованих моделях для інференсу на CPU та мобільних пристроях.Потребує відновлення масштабу (scale) та зміщення (zero_point).Квантизоване значення: 25Бінарне значення: 00011001Фактичне значення: 25 × 0.125 = 3.125Можливі типи квантування: Q8_0, Q8_1, Int8Affine, PerChannelQuant (ONNX), dynamic/int8 (TensorFlow Lite)
Формат: INT4 (Q4), 4 біти, scale = 0.5 (2 числа в 1 байті)Призначення: Дуже стиснутий формат для мовних моделей. Використовується в llama.cpp, GGUF та інших системах.Забезпечує суттєве зменшення розміру моделі. Потребує відновлення (деквантування) під час запуску.Квантизоване значення:7 (максимальне значення для signed 4-бітного int: -8…+7)Бінарне значення: 0111Фактичне значення: 7 × 0.5 = 3.5Можливі типи квантування:Q4_0, Q4_1, Q4_K, Q4_G, Q4_M (llama.cpp, GGUF)
Формат: INT2 (Q2), 2 біти, scale = 1.0 (4 числа в 1 байті)Призначення: Екстремально стиснутий формат для використання в LLM на пристроях з обмеженими ресурсами.Використовується в деяких варіантах GGUF, MLC, а також в експериментах з екстремальним квантуванням.Квантизоване значення: 1 (максимум серед значень: -2…+1)Бінарне значення: 01Фактичне значення: 1 × 1.0 = 1.0Можливі типи квантування: Q2_K (llama.cpp, GGUF)
Формат: INT1 (Q1), 1 біт, scale = 2.0 (8 чисел в 1 байті)Призначення: Мінімально можлива точність. Використовується в бінарних нейромережах та прототипах.Зазвичай значення -1 або +1. Застосовується рідко в LLM, але може бути корисним у BNN (Binary Neural Networks).Квантизоване значення: 1Бінарне значення: 1Фактичне значення: 1 × 2.0 = 2.0Можливі типи квантування: Q1, BinaryNet, XNOR-Net (частіше в академічних/експериментальних BNN)Також моделі можуть мати додаткові параметри квантування:
_K- Позначає "K-блокову" квантизацію (K-Block Quantization).- Ваги розбиваються на блоки фіксованої довжини (наприклад, по 32 або 64 значення).- Усередині кожного блоку використовується спільний scale і zero_point.- Це дозволяє суттєво зменшити розмір моделі, зберігаючи вищу точність порівняно з простою квантизацією.- Приклади форматів: Q2_K, Q4_K, Q6_K, Q8_K
_0, _1- Показують, яку схему квантування застосовують: - _0: базова схема, без зміщень (bias), один scale на блок - _1: покращена схема, з додатковими bias або scale-зсувами- Використовуються у форматах: Q4_0, Q4_1, Q5_0, Q5_1, Q8_0, Q8_1- Як правило, _1 забезпечує кращу точність за незначного збільшення розміру
Мітки рівня стиснення/точності: K_L, K_M, K_S, K_G
K_L- Kvantization Low- Низький розмір моделі, мінімальна точність- Підходить для пристроїв з украй обмеженими ресурсами- Максимально агресивна квантизація
K_M- Kvantization Medium- Середній компроміс між точністю та обсягом- Підходить для більшості локальних завдань
K_S- Kvantization Small- Модель максимально стиснута за розміром, навіть на шкоду якості- Часто використовується як референс для екстремального стиснення
K_G- Kvantization General- Збалансована модель: розумний компроміс між швидкістю, якістю та розміром- Добре працює на більшості CPU
Приклад назви моделі:"mistral-7b.Q4_K_M.gguf" — це означає:- Модель Mistral 7B- Використовується квантування Q4_K (4-бітна K-блокова)- Рівень компромісу: MediumПродуктове квантування (Product Quantization)
Метод, який дозволяє сильно стиснути вектори ознак (ембединги), розбиваючи їх на частини й кодуючи кожну частину через найближчий шаблон (кластер).
Як це працює:
Є вектор (наприклад, розміром 128 чисел). Це може бути ембединг тексту або зображення від нейромережі.
Розбиваємо вектор на шматки — наприклад, 8 частин по 16 чисел (128 / 8 = 16).
Для кожної позиції шматка навчаємо свій кодувальник (квантайзер) — на великих даних заздалегідь обираються, які "шаблони" (центри кластерів) схожі на можливі шматки.
Наприклад:
перший шматок може бути схожий на шаблон №12,
другий — на шаблон №3,
третій — на шаблон №88,
і так далі.
Зберігаємо лише номери цих шаблонів. Замість зберігання 128 чисел (float32 = 512 байт), зберігаємо, наприклад, 8 чисел (по 1 байту на шаблон) — усього 8 байт.
*QAT (Quantization-Aware Training) квантування:* це метод квантування нейронних мереж, при якому квантування враховується вже під час навчання моделі. Він дозволяє домогтися майже такої самої точності, як і в оригінальної моделі з float-параметрами, при цьому модель використовуватиме компактніші int8 або інші низькорозрядні формати, придатні для ефективного запуску на пристроях з обмеженими ресурсами (наприклад, смартфонах або мікроконтролерах).
Як працює QAT — поетапно:
Модель у float32:
Навчання починається зі звичайної моделі, що використовує числа з плаваючою точкою (зазвичай float32). Це забезпечує високу точність і стабільність навчання.
Імітація квантування під час forward pass (fake quantization):
При кожному проході вперед значення (ваги, активації) емулюються як квантовані, тобто вони перетворюються на int8, а потім назад на float32. Це дає можливість моделі «побачити» помилки квантування ще на стадії навчання.
float32 → int8 → float32
Таким чином, під час зворотного поширення (backpropagation) градієнти рахуються за float32-версією, але помилки через квантування все одно впливають на навчання.
Оновлення параметрів (backward pass):
Градієнти рахуються як зазвичай, але з урахуванням спотворень від fake quantization. Це дозволяє моделі адаптуватися до того, що ваги й активації будуть згодом використовуватися в низькій точності.
Експорт фінальної моделі в int8:
Після завершення навчання ваги дійсно квантуються в int8, і модель може бути скомпільована й запущена у виробничому середовищі.
Що саме квантується:
Ваги (weights) — float32 → int8
Активації (activations) — float32 → int8
(Іноді також квантуються градієнти й проміжні стани, але це рідкість.)
Навіщо використовувати QAT:
Вища точність, ніж у post-training quantization (PTQ)
Низьке споживання пам’яті
Швидше виконання на CPU/GPU/NPUs з підтримкою int8
Особливо важливо для мобільних та embedded-пристроїв (наприклад, Android)
Далі модель відправляється в трансформер.
Трансформер (Transformer)
Архітектура нейромережі, яка стала основою для сучасних мовних моделей, таких як ChatGPT, BERT, LLaMA, Gemma і багатьох інших.
Він був уперше описаний у науковій статті 2017 року під назвою “Attention is All You Need”.
Простіше кажучи, трансформер — це “розумна машина”, яка вміє читати й розуміти текст, обробляючи всі слова одночасно, а не по одному, як це робили попередні моделі (наприклад, RNN, LSTM).
Шари трансформера (Transformer Layers)
це повторювані блоки, крізь які проходить кожен токен усередині моделі.
Один шар (або “трансформер-блок”) включає в себе:
*Self-Attention:* токен “дивиться” на інші токени й вирішує, хто важливий.
Feed-Forward Network:
уточнення й перетворення кожного токена.
Layer Normalization або LayerNorm:
стабілізація обчислень.
Residual Connections:
щоб модель не “забула” початкову інформацію.
LayerNorm може бути до або після Residual (Pre-LN vs Post-LN).
та інші етапи.
Кожен шар трансформера (Transformer Block) — це окремий набір параметрів, які:
незалежно навчаються,
незалежно застосовуються до вхідних даних,
дають дедалі “глибше” розуміння змісту й контексту.
Що означає “32 шари”:
кожен токен проходить крізь 32 такі операції, послідовно одну за одною, після кожного шару токен стає дедалі більш поінформованим, тобто його представлення (вектор) дедалі глибше відображає контекст
Кількість шарів моделей (transformer layers):
| Модель | Кількість шарів ||-----------------------|------------------|| LLaMA 1 (7B) | 32 || LLaMA 2 (7B) | 32 || LLaMA 2 (13B) | 40 || LLaMA 2 (70B) | 80 || LLaMA 3 (8B) | 32 || LLaMA 3 (70B) | 80 || Gemini 1.0 Pro | 32 || Gemini 1.0 Ultra | 64 || Gemini 1.5 Pro | 64 || Gemma 1 (2B) | 18 || Gemma 1 (7B) | 28 || Gemma 2 (9B) | 32 || Gemma 2 (27B) | 40 || Gemma 3 (12B) | 36 || Gemma 3 (27B) | 48 || Qwen 1.5 (7B) | 32 || Qwen 2 (7B) | 28 || Qwen 2.5 (7B) | 28 || DeepSeek LLM (7B) | 30 || DeepSeek LLM (67B) | 95 || DeepSeek-Coder (6.7B) | 32 || DeepSeek-Coder (33B) | 64 || DeepSeek-R1 | 95 |Активація (Activation)
це проміжні виходи нейромережі після застосування функцій і шарів).
Можна сказати, що активації це дані, які "живуть усередині мережі" на кожному етапі проходу входу крізь модель.
Тензор (Tensor)
у нейромережі це багатовимірний масив чисел, з яким працюють шари моделі.
Вхідні дані:
Текст → токени → ембединги → тензор batch_size × seq_len × embedding_dim.
Ваги моделі:
Вагові матриці в шарах — теж тензори.
Проміжні представлення (активації):
Вихід кожного шару (наприклад, LayerNorm, Attention) — тензор.
Градієнти:
При навчанні модель рахує градієнти (тензори) для оновлення ваг.
Крок 3: Positional Encoding / Embeddings
Без додаткової інформації фрази:
“Кіт їсть рибу”
“Рибу їсть кіт”
могли б сприйматися однаково, адже набір слів однаковий.
Щоб дати моделі відчуття порядку, кожному токену (слову або частині слова) додається позиційний вектор — набір чисел, який повідомляє моделі, на яких позиціях розташовані слова.
Token embedding + Positional Encoding / Embeddings = Сумарний вектор
Positional Encoding / Embedding це:
Вектор такої самої розмірності, як у токена (d)
Представляє позицію в послідовності
Може бути заданий формулою (sin/cos) або навчаний
Об’єднується з вектором токена на вході в модель
Який тип використовується в моделях:
| Метод | Модель/сімейство | Опис ||-----------------------------|-----------------------------|-----------------------------------------------|| Sinusoidal Encoding | Transformer | Не навчаються, засновані на синусах і || | (Vaswani et al., 2017) | косинусах з різною частотою ||-----------------------------|-----------------------------|-----------------------------------------------|| Learnable Embeddings | BERT, GPT-2, | Навчувана таблиця позицій, схожа на || | DistilBERT, ELECTRA | ембединги слів ||-----------------------------|-----------------------------|-----------------------------------------------|| Rotary Positional Embedding | GPT-NeoX, LLaMA, LLaMA 2/3, | Обертання векторів — зберігає відносні || (RoPE) | ChatGLM, Mistral | позиції між токенами ||-----------------------------|-----------------------------|-----------------------------------------------|| ALiBi | OPT, BLOOM, Phi-2 | Лінійний bias, додається до attention score, || | | не потребує зберігання позицій ||-----------------------------|-----------------------------|-----------------------------------------------|| Relative Position Bias | T5, DeBERTa, Transformer-XL,| Використовує зміщення між токенами замість || | Pegasus, LongT5 | абсолютних позицій |Приклад:
Текст користувача:"Привіт, світ"
ID токена у словнику моделі для слова:"Привіт" = 1123"," = 15"світ" = 345Отримуємо масив ID токенів:["Привіт", ",", "світ"] = [1123, 15, 345]
Token embedding з бази моделі для:ID токена 1123 = [0.034, 0.120, 0.905, ..., 0.028]ID токена 15 = [0.022, -0.010, -0.313, ..., 0.117]ID токена 345 = [-0.102, 0.241, 0.543, ..., 0.055]
Позиція ID токенa слова в тексті користувача:[1123, 15, 345] = [позиція 0, позиція 1, позиція 2]
Обчислюємо або отримуємо з бази моделі вектор позиції:Вектор позиції 0 = [0.001, 0.087, -0.432, ..., 0.019]Вектор позиції 1 = [0.005, -0.013, 0.021, ..., -0.012]Вектор позиції 2 = [-0.003, 0.099, -0.082, ..., 0.003]
Сумарний вектор для ID 1123:Token embedding [0.034, 0.120, 0.905, ..., 0.028] +Вектор позиції 0 [0.001, 0.087, -0.432, ..., 0.019] =[0.035, 0.207, 0.473, ..., 0.047]аналогічно для решти токенівОбчислення Sinusoidal Position Encoding
Синусоїдальне позиційне кодування використовується в Transformer-моделях для надання інформації про позицію токенів у вхідній послідовності. На відміну від рекурентних мереж, Transformer не має вбудованого механізму врахування порядку токенів. Тому необхідно явно закодувати інформацію про позицію кожного токена.
З точки зору математики:
pos - позиція токена в послідовності (починаючи з 0)i - індекс виміру вектора позиційного кодування (починаючи з 0)d_model - розмірність вектора позиційного кодування (розмірність ембединга)PE(pos, i) - i-й елемент вектора позиційного кодування для позиції pos.10000 - це гіперпараметр. Він використовується для масштабування позиції та частоти синусоїд. Вибір цього значення дозволяє моделі легко екстраполювати на послідовності, довші за ті, на яких вона була навчена.
Тоді i-й елемент вектора позиційного кодування для позиції pos:PE(pos, 2i) = sin(pos / 10000^(2i/d_model))PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))Формула використовує різні частоти (довжини хвиль) синусоїдальних функцій для різних вимірів вектора позиційного кодування.
Парні виміри використовують синус, а непарні — косинус. Це дозволяє моделі розрізняти позиції за різними фазами й амплітудами.
Ділення pos на 10000^(2i/d_model) зменшує частоту синусоїди зі збільшенням індексу виміру i.
Це створює повільніші коливання для вищих вимірів, що дозволяє моделі розрізняти позиції в різних масштабах.
Приклад:
pos = 0 (перший токен)d_model = 4 (розмірність вектора позиційного кодування)
Тоді вектор позиційного кодування PE(0) матиме розмірність 4.Обчислимо кожен елемент:
i = 0:PE(0, 0) = sin(0 / 10000^(2*0/4)) = sin(0) = 0PE(0, 1) = cos(0 / 10000^(2*0/4)) = cos(0) = 1
i = 1:PE(0, 2) = sin(0 / 10000^(2*1/4)) = sin(0) = 0PE(0, 3) = cos(0 / 10000^(2*1/4)) = cos(0) = 1
PE(0) = [0, 1, 0, 1]
Тепер обчислимо вектор позиційного кодування для pos = 1:
i = 0:PE(1, 0) = sin(1 / 10000^(2*0/4)) = sin(1) ≈ 0.8415PE(1, 1) = cos(1 / 10000^(2*0/4)) = cos(1) ≈ 0.5403
i = 1:PE(1, 2) = sin(1 / 10000^(2*1/4)) = sin(0.01) ≈ 0.01PE(1, 3) = cos(1 / 10000^(2*1/4)) = cos(0.01) ≈ 1
PE(1) = [0.8415, 0.5403, 0.01, 1]
Важливо:
У реальних Transformer-моделях позиційне кодування зазвичай обчислюється для всіх можливих позицій у послідовності заздалегідь і зберігається як таблиця.
Потім, при подачі вхідної послідовності, відповідні вектори позиційного кодування додаються до ембедингів токенів.
Вибір гіперпараметра 10000 є емпіричним і може бути налаштований залежно від конкретного завдання.
Відносні позиційні зміщення:
Замість того щоб кодувати абсолютну позицію кожного токена, деякі варіанти трансформерів вводять відносні позиційні зміщення. Це дозволяє моделі напряму враховувати відстань (і напрямок) між парами токенів, а не їхню «глобальну» позицію в послідовності.
Навіщо потрібні відносні зміщення:
При генерації або обробці довгих текстів важливо, як далеко одне від одного лежать слова, а не тільки їхні абсолютні індекси.
Абсолютні ембединги погано узагальнюються на довші послідовності, ніж ті, на яких модель навчалася.
Відносні зміщення дають більшу гнучкість: модель вчиться, наприклад, «що на 3 позиції праворуч може бути об’єкт дії», незалежно від того, де це речення стоїть у тексті.
Transformer-XL, відносні позиції через зміщення ключів і запитів:
У класичному self-attention рахуємо:score_{i,j} = (Q_i · K_j) / sqrt(d_k)
У Transformer-XL вводять два додаткові набори ембедингів:E^R[r] — ембединг для відносного зсуву r = j - iU, V — два вектори-зсуви
Підсумкова формула:
Q_i·E^R[j-i] - контент-залежна частина уваги, що враховує відносне зміщенняV·E^R[j-i] - контент-незалежна частина, що задає базовий bias для цього зміщення
score_{i,j} = (1/√d_k) * (Q_i·K_j + Q_i·E^R[j-i] + U·K_j + V·E^R[j-i])Крок 4: Attention, Self-Attention (Вектор Уваги, Q,K,V-проєкції) і Розбиття на Голови Уваги (Multi-Head Attention)
Тепер, коли токени мають не тільки “значення”, а й “позицію”, вони проходять через механізм self-attention, завдяки якому кожне слово дивиться на інші слова, щоб вирішити, на кого з них мені звернути увагу, щоб краще зрозуміти свій зміст.
Це схоже на те, як людина читає речення: вона може повернутися очима до попередніх слів, щоб правильно зрозуміти контекст.
Self-Attention відповідає за розуміння контексту — які токени важливі один для одного, і дозволяє кожному токену зважено дивитися на всі інші в послідовності та визначати, на що звернути увагу при побудові змісту.
Softmax
це функція, яка бере на вхід вектор чисел і перетворює його на розподіл імовірностей, де всі значення невід’ємні й у сумі дають 1.
softmax(zᵢ) = exp(zᵢ) / ∑ⱼ exp(zⱼ)
Модель створює три представлення для кожного токена:
Q (питання): що я шукаю?
K (ключ): що я можу запропонувати?
V (значення): яку інформацію я несу?
Кожне слово порівнює свій Q з K усіх інших слів, щоб дізнатися, на кого варто дивитися. Після цього воно збирає потрібну інформацію з V тих слів, які виявилися важливими.
Для обчислення представлень використовується матриця ваг шару — це навчуваний параметр нейромережі, тобто вона ініціалізується випадково при створенні моделі й навчається разом з рештою ваг. Спочатку випадкова, потім стає «розумною» за рахунок навчання.
Як вони навчаються:
На етапі зворотного поширення (backpropagation) модель порівнює свої передбачення з правильною відповіддю (наприклад, наступний токен) і оновлює Wq, Wk, Wv за градієнтом помилки за допомогою оптимізатора (наприклад, Adam).
Параметри Wq, Wk і Wv можуть бути спільними або різними для кожної з голів, залежно від моделі.
Wq (Query Projection):
Створює “питання” — що токен хоче знайти в інших токенах.
Визначає напрямок уваги.
Q токена = Сумарний вектор токена X * Вага шару WqWk (Key Projection):
Створює “ключ” — що кожен токен пропонує іншим.
Використовується для порівняння з Q (наскільки Qᵢ “схожий” на Kⱼ).
K токена = Сумарний вектор токена X * Вага шару WkWv (Value Projection):
Створює “інформацію”, яку токен може передати, якщо на нього звернули увагу.
V токена = Сумарний вектор токена X * Вага шару Wv
Голова уваги (attention-head):
Також у моделей існують Голова уваги (attention-head). Кожна голова уваги (attention-head) обробляє вхідний вектор по-своєму, через свої Q, K, V проєкції, і дивиться на різні аспекти речення.
У кожної attention-head є своя точка зору:
одна голова може відстежувати граматику (наприклад, підмет і присудок),
інша — змістові зв’язки (наприклад, хто на що діє),
третя — позиції, контекст тощо.
Замість однієї “точки зору” — одразу декілька.
Кожна голова бачить увесь текст, але — аналізує його по-своєму, через проєкцію та увагу.
Приклад:
У реченні
“Хлопчик, який тримав м’яч, утік.”
Різні голови можуть бачити
Голова 1: “хлопчик” ↔ “утік” → хто виконує дію
Голова 2: “який” ↔ “тримав” → вкладений граматичний зв’язок
Голова 3: “м’яч” ↔ “тримав” → об’єкт дії
Кожна голова видає своє представлення для кожного токена, з урахуванням своїх “спостережень”.
*Архітектура Multi-Head Attention (MHA):*
Класична реалізація self-attention, як в оригінальній статті “Attention is All You Need”.
Що відбувається:
Є кілька голів
Кожна голова має свої Wq, Wk, Wv
Кожна голова по-своєму аналізує весь контекст
Результати всіх голів об’єднуються і проходять через спільну Wo
Плюси:
Гнучкість: кожна голова “дивиться” на вхід по-своєму
Чудово працює при великих обчислювальних ресурсах
Мінуси:
Дуже дорого за пам’яттю і швидкістю при великій кількості голів
Особливо при довгих послідовностях
*Архітектура Multi-Query Attention (MQA):*
Оптимізована версія уваги, що використовується в GPT-3.5, PaLM, Gemma та інших, щоб знизити навантаження на пам’ять і пришвидшити інференс.
Що відбувається:
По одній Wq на голову
Тільки один Wk і один Wv
Усі голови використовують одні й ті самі ключі та значення
Плюси:
Менше пам’яті: K і V зберігаються в одному примірнику
Швидша генерація: менше даних зберігається між кроками
Мінуси:
Менше гнучкості (усі голови “дивляться” на однакові K і V)
Може трохи погіршити якість на складних завданнях
Архітектура Grouped Query Attention (GQA):
комбінований підхід з попередніх 2х
Приклад:
Наприклад, в архітектурі нейромережі Gemma 3 використовується Grouped Query Attention (GQA) — компроміс між стандартним Multi-Head Attention (MHA) і Multi-Query Attention (MQA). У цій схемі матриці Wq (для запитів) різні для кожної голови, тоді як матриці Wk (для ключів) і Wv (для значень) можуть бути спільними для груп голів.
У цій нейромережі:
Wq: кожна голова має свою унікальну матрицю Wq, що дозволяє кожній голові фокусуватися на різних аспектах вхідної послідовності.
Wk і Wv: голови діляться на групи, і всередині кожної групи використовується спільна матриця Wk і Wv. Це знижує обсяг обчислень і пам’яті, необхідних для зберігання ключів і значень.
Це означає, що 8 голів запитів (Wq) діляться на 4 групи, кожна з яких використовує спільні матриці ключів і значень (Wk і Wv).
У підсумку, модель Gemma 3 розміром 27B має
80 шарів і 64 голови, всередині будуть:
64 різних Wq (Query Projection)
8 різних Wk (Key Projection)
8 різних Wv (Value Projection)
64 різних Wo (Output Projection)
80 MLP з різними вагами (Feed-Forward Network)
80 LayerNorm з різними гамма/бета-параметрами
Кількість голів уваги (attention heads) популярних моделей:
| Модель | К-сть attention heads ||-------------------------|------------------------|| LLaMA 1/2 | 32 || LLaMA 3 | 32 / 64 / 128 || Gemini 1.0/1.5 | невідомо || Gemma 1/2 | 16 || Gemma 3 | 8 || Qwen 1.5 | 40 (Q) / 8 (KV) || Qwen 2 | 40 (Q) / 8 (KV) || Qwen 2.5 | 40 (Q) / 8 (KV) || DeepSeek LLM | 32 (7B) / GQA (67B) || DeepSeek-Coder | 16 / 32 / 56 || DeepSeek V2/V3 | 128 || DeepSeek-R1 | 128 |
*Каузальне маскування (Causal Masking):*
застосовується в деяких моделях. Коли мовна модель (LLM) генерує текст, вона має передбачати наступне слово на основі лише попередніх слів.
Це означає, що Токен не повинен мати доступу до токенів, які йдуть після нього.
Щоб забезпечити це обмеження, використовується каузальне маскування — це механізм, який “забороняє” увазі бачити майбутні токени.
Як це працює:
У звичайному self-attention кожен токен “дивиться” на всі токени в послідовності, включно з майбутніми.
При генерації це неприйнятно, тому що це було б “шахрайством” — модель бачить відповідь заздалегідь.
Щоб цьому запобігти, при обчисленні attention застосовують маску — спеціальну матрицю, що називається attention mask.
Для послідовності довжини n створюється трикутна маска, у якій:
Значення вище діагоналі замінюються на -∞ або велике від’ємне число.
Після цього застосовується softmax, і ці значення перетворюються на нульову увагу.
Приклад маски (для n = 4 токенів):[ [0, -∞, -∞, -∞], [0, 0, -∞, -∞], [0, 0, 0, -∞], [0, 0, 0, 0]]
Це означає:Токен 0 бачить лише себе.Токен 1 бачить себе і токен 0.Токен 2 бачить себе, токен 1 і токен 0.Токен 3 бачить усе, що до нього.
Де використовується Causal Masking:
GPT, LLaMA, Mistral, Gemma та інші автогенеративні моделі обов’язково використовують каузальне маскування.
BERT, навпаки, використовує bidirectional attention — токен може бачити весь контекст (зокрема майбутнє), тому що завдання інше — не генерація, а розуміння.
Чому це важливо, без causal masking:
Модель при навчанні буде “підглядати” на правильну відповідь (наступний токен).
Це призведе до поганої генерації при використанні моделі в inference, коли майбутнє невідоме.
Роль позиції в Self-Attention:
Без Position Encoding, Self-Attention бачить лише зміст слів, але не їхній порядок. З доданим Position Encoding, Self-Attention починає враховувати не тільки, що написано, а й де це розташовано:
“Хлопчик” раніше “читав” → можливо, він суб’єкт
“Книга” поруч із “читав” → найімовірніше, об’єкт
Що в підсумку робить трансформер:
Кожне слово отримує інформацію про свою позицію
Через self-attention кожне слово “запитує” всі інші:
“Що ви значите для мене в цьому контексті?”
Модель складає ці відповіді й отримує глибоке розуміння змісту всієї фрази
Повторює це на кожному шарі (зазвичай 12–40 разів), поглиблюючи “розуміння”
Текст користувача:"Привіт, світ"
ID токена зі словника моделі:"Привіт" = 1123"," = 15"світ" = 345Отримуємо масив ID токенів:["Привіт", ",", "світ"] = [1123, 15, 345]
Token embedding або вектор уваги з бази моделі:"Привіт" = ID 1123 = [0.034, 0.120, 0.905, ..., 0.028]"," = ID 15 = [0.022, -0.010, -0.313, ..., 0.117]"світ" = ID 345 = [-0.102, 0.241, 0.543, ..., 0.055]
Обчислюємо або отримуємо з бази моделі вектор позиції:Вектор позиції "Привіт" = [0.001, 0.087, -0.432, ..., 0.019]Вектор позиції "," = [0.005, -0.013, 0.021, ..., -0.012]Вектор позиції "світ" = [-0.003, 0.099, -0.082, ..., 0.003]
Для "Привіт":Token embedding [0.034, 0.120, 0.905, ..., 0.028] +Вектор позиції [0.001, 0.087, -0.432, ..., 0.019] =Сумарний вектор X [0.035, 0.207, 0.473, ..., 0.047]
Обчислюємо матрицю ваг для шару для кожного токена і для кожної голови уваги:Q = X * Wq головиK = X * Wk головиV = X * Wv головиде X це сумарний вектор для токенаWq, Wk, Wv - спільні й однакові для всіх токенів у цьому шарі,але різні або частково різні для кожної голови
Наприклад:Wq голови = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9], [1.0, 1.1, 1.2]]Wk голови = [[0.12, 0.22, 0.32], [0.42, 0.52, 0.62], [0.72, 0.82, 0.92], [1.02, 1.12, 1.22]]Wv голови = [[0.11, 0.21, 0.31], [0.41, 0.51, 0.61], [0.71, 0.81, 0.91], [1.01, 1.11, 1.21]]
Тоді:
Q "Привіт" = x * Wq = [0.035, 0.207, 0.473, 0.047] * Wq
Q[0] = 0.035 * 0.1 + 0.207 * 0.4 + 0.473 * 0.7 + 0.047 * 1.0 ≈ 0.0035 + 0.0828 + 0.3311 + 0.047 ≈ 0.4644
Q[1] = 0.035*0.2 + 0.207*0.5 + 0.473*0.8 + 0.047*1.1 = 0.007 + 0.1035 + 0.3784 + 0.0517 ≈ 0.5406
Q[2] = 0.035*0.3 + 0.207*0.6 + 0.473*0.9 + 0.047*1.2 = 0.0105 + 0.1242 + 0.4257 + 0.0564 ≈ 0.6168
Q = [0.4644, 0.5406, 0.6168]
аналогічним чином обчислюємо для "Привіт":K = [0.47964, 0.55684, 0.63204]V = [0.47299, 0.54822, 0.62442]і для інших токенів
*Обчислення Attention Score між токенами:* На цьому етапі ми отримали Q, K і V для одного токена й однієї голови уваги. Далі можна перейти до обчислення attention score між токенами, наприклад, між “Привіт” і “світ”, з використанням формули:
З точки зору математики:
Q - матриця запитів (Queries)K - матриця ключів (Keys)V - матриця значень (Values)d_k - розмірність вектора
Attention(Q, K, V) = softmax((Q * Kᵀ) / √d_k + mask) * V
Q * Kᵀ - матричне множення Q на транспоновану матрицю K.Це обчислює "сирі" ваги уваги між кожним запитом і кожним ключем.
√d_k - квадратний корінь із розмірності вектора ключа (dimension of key vectors).Використовується для масштабування, щоб запобігти надто великим значенняму softmax, що може призвести до проблем з градієнтами.Це називається Scaled Dot-Product Attention.
mask - це матриця, яка використовується для обмеження уваги.У генеративних моделях застосовується causal mask (каузальне маскування) —вона не дозволяє токену "бачити вперед" під час обчислення уваги.Це критично для завдань, де модель передбачає наступний токен.
softmax(...) - функція softmax застосовується до результату ділення Q ⋅ Kᵀна √d_k (і додавання mask, якщо вона є). Це нормалізує ваги увагитак, щоб вони сумувалися в 1 для кожного запиту.
Множення результату softmax на V дає зважену суму значень,де ваги визначаються attention score.Приклад:
Вхідні дані:
| Токен | Q-вектор | K-вектор | V-вектор ||----------|--------------------------|--------------------------|------------------------|| "Привіт" | [0.4644, 0.5406, 0.6168] | [0.4796, 0.5568, 0.6320] | [0.473, 0.548, 0.624] ||----------|--------------------------|--------------------------|------------------------|| "," |[0.2, 0.3, 0.1] | [0.25, 0.35, 0.15] | [0.12, 0.09, 0.04] ||----------|--------------------------|--------------------------|------------------------|| "світ" | [0.55, 0.33, 0.77] | [0.5213, 0.5967, 0.6721] | [0.55, 0.65, 0.75] |
Для простоти використовуємо розмірність d_k для Q, K = 3 (для прикладу)
Обчислення:
Привіт–Привіт: 0.4644*0.4796 + 0.5406*0.5568 + 0.6168*0.6320 ≈ 0.2228 + 0.3011 + 0.3899 = 0.9138Привіт–",": 0.4644*0.25 + 0.5406*0.35 + 0.6168*0.15 ≈ 0.1161 + 0.1892 + 0.0925 = 0.3978Привіт–світ: 0.4644*0.5213 + 0.5406*0.5967 + 0.6168*0.6721 ≈ 0.2422 + 0.3225 + 0.4147 = 0.9794","–Привіт: 0.2*0.4796 + 0.3*0.5568 + 0.1*0.6320 ≈ 0.0959 + 0.1670 + 0.0632 = 0.3261","–",": 0.2*0.25 + 0.3*0.35 + 0.1*0.15 = 0.05 + 0.105 + 0.015 = 0.17","–світ: 0.2*0.5213 + 0.3*0.5967 + 0.1*0.6721 ≈ 0.1043 + 0.1790 + 0.0672 = 0.3505світ–Привіт: 0.55*0.4796 + 0.33*0.5568 + 0.77*0.6320 ≈ 0.2638 + 0.1837 + 0.4876 = 0.9351світ–",": 0.55*0.25 + 0.33*0.35 + 0.77*0.15 ≈ 0.1375 + 0.1155 + 0.1155 = 0.3685світ–світ: 0.55*0.5213 + 0.33*0.5967 + 0.77*0.6721 ≈ 0.2867 + 0.1969 + 0.5185 = 1.0021
Ділимо на √d_k = √3 ≈ 1.732
| From \ To | Привіт | "," | світ || --------- | ----------------------- | ----------------------- | ----------------------- || Привіт | 0.9138 / 1.732 ≈ 0.5275 | 0.3978 / 1.732 ≈ 0.2296 | 0.9794 / 1.732 ≈ 0.5652 || "," | 0.3261 / 1.732 ≈ 0.1882 | 0.17 / 1.732 ≈ 0.0982 | 0.3505 / 1.732 ≈ 0.2023 || світ | 0.9351 / 1.732 ≈ 0.5397 | 0.3685 / 1.732 ≈ 0.2127 | 1.0021 / 1.732 ≈ 0.5786 |
Застосуємо експоненту для "Привіт":exp(0.5275) ≈ 1.694exp(0.2296) ≈ 1.258exp(0.5652) ≈ 1.759
Сума всіх експонент для "Привіт":
1.694 + 1.258 + 1.759 ≈ 4.711
softmax = exp(x) / сума всіх exp(x)
Softmax вага для "Привіт" відносно інших токенів:
| Токен | Softmax вага || ------ | -------------------- || Привіт | 1.694 / 4.711 ≈ 0.36 || "," | 1.258 / 4.711 ≈ 0.27 || світ | 1.759 / 4.711 ≈ 0.37 |
Коли "Привіт" генерує своє представлення (на виході attention шару), він:бере 36% інформації від самого себе,27% від ",",і 37% від слова "світ".
Зважене підсумовування:
Output= 0.36 ∗ V("Привіт") + 0.27 ∗ V(",") + 0.37 ∗ V("світ")
Координата 1 = 0.36∗0.473+0.27∗0.12+0.37∗0.55≈0.1703+0.0324+0.2035 ≈ 0.4062Координата 2 = 0.36∗0.548+0.27∗0.09+0.37∗0.65≈0.1973+0.0243+0.2405 ≈ 0.4621Координата 3 = 0.36∗0.624+0.27∗0.04+0.37∗0.75≈0.2246+0.0108+0.2775 ≈ 0.5129
Attention Score для токена "Привіт": [0.4062, 0.4621, 0.5129]
Цей вектор Attention Score — нове представлення токена “Привіт”, у якому враховано його контекст: і сам він, і сусіди. Саме такі вектори потім ідуть або в наступний attention-шар, або на вихід моделі.
Порівняння токена самого із собою потрібне тому, що в self-attention кожен токен “дивиться” на всі токени, включно із самим собою.
Це необхідно для того, щоб:
Зберегти інформацію про сам токен — інакше він би “губився” на тлі решти.
Навчитися “підсилювати” або “пригнічувати” себе — наприклад, у деяких мовних ситуаціях токен важливий сам по собі (наприклад, особовий займенник), а іноді його контекст важливіший.
Забезпечити симетрію — матриця уваги завжди квадратна (n × n), і по діагоналі — це якраз self-to-self attention.
Формально attention — це ваги, за якими токен агрегує інформацію від інших токенів (зокрема від самого себе), і w1 — це увага “Привіт” до самого себе. Якщо його не рахувати — токен “Привіт” взагалі не брав би участі у своєму власному виході.
Приклад:
Він сказав, що він прийде.
Коли модель обробляє токен “він”, їй потрібно:
“подивитися” на інші токени — щоб зрозуміти контекст,
але й сам токен “він” теж важливий, щоб не втратити інформацію про те, про кого йдеться.
Матеріали:
Wikipedia: Attention (machine learning)
Understanding Q,K,V In Transformer( Self Attention)
What is Query, Key, and Value (QKV) in the Transformer Architecture and Why Are They Used?
Крок 5: Об’єднання голів уваги (Head Concatenation, Concatenated Multi-Head Attention)
Попередні розрахунки відбувалися для кожної з голів уваги, тепер необхідно об’єднати результати. Нагадаю, результати різні через різні Wq, Wk, Wv для різних голів.
З точки зору математики:
h - кількість голів увагиd_k - розмірність вектора Attention Score для кожної головиAttentionScore_i - вектор Attention Score для i-ї голови.
Concatenation (Об’єднання векторів в один вектор за координатами):
concat = [AttentionScore_1, AttentionScore_2, ..., AttentionScore_h]Приклад:
Вхідні дані:Припустимо, розмірність: 6 (2 голови × по 3 значення)
Кількість голів уваги h = 2Розмірність вектора d_k = 3"Привіт" для голови 1 AttentionScore_1 = [0.4062, 0.4621, 0.5129]"Привіт" для голови 2 AttentionScore_2 = [0.22, 0.33, 0.44]
Обчислення:
Concatenation (Об’єднання векторів в один вектор за координатами):
concat[0] = AttentionScore_1[0] = 0.4062concat[1] = AttentionScore_1[1] = 0.4621concat[2] = AttentionScore_1[2] = 0.5129concat[3] = AttentionScore_2[0] = 0.22concat[4] = AttentionScore_2[1] = 0.33concat[5] = AttentionScore_2[2] = 0.44
concat = [0.4062, 0.4621, 0.5129, 0.22, 0.33, 0.44]Крок 6: Output Projection (Wo)
Після об’єднання attention-виходів з різних голів, вони зазвичай пропускаються через лінійний шар (Dense Layer), щоб повернути їх у вихідний простір розмірності моделі.
З точки зору математики:
concat ∈ ℝ⁶ - вхідний векторWₒ ∈ ℝ⁶ˣ³ - матриця ваг проєкціїoutput ∈ ℝ³ - вихідний вектор після проєкції
Множення:output = concat • Wₒ
Докладна формула для кожної компоненти вихідного вектора:output[0] = concat[0]*W_o[0][0] + concat[1]*W_o[1][0] + concat[2]*W_o[2][0] + concat[3]*W_o[3][0] + concat[4]*W_o[4][0] + concat[5]*W_o[5][0]
Альтернативно, у вигляді суми:output_j = ∑_{i=1}^{6} concat_i ⋅ W_o[i][j] для j = 1, 2, 3
Матрично:output = concat (1x6) ⋅ W_o (6x3) = (1x3)Приклад:
Вхідні дані:Розмірність моделі d_model = 3
Отже, проєкційна матриця W_o матимерозмірність (6, 3), тобто 6 входів → 3 виходиW_o = [ [0.1, 0.2, 0.3], [0.0, 0.1, 0.0], [0.2, 0.0, 0.1], [0.1, 0.2, 0.2], [0.0, 0.1, 0.3], [0.3, 0.0, 0.1]]
Вхідний вектор після concat:concat = [0.4062, 0.4621, 0.5129, 0.22, 0.33, 0.44]
Обчислення:
x = 0.4062*0.1 + 0.4621*0.0 + 0.5129*0.2 + 0.22*0.1 + 0.33*0.0 + 0.44*0.3 = 0.04062 + 0 + 0.10258 + 0.022 + 0 + 0.132 = 0.2972
y = 0.4062*0.2 + 0.4621*0.1 + 0.5129*0.0 + 0.22*0.2 + 0.33*0.1 + 0.44*0.0 = 0.08124 + 0.04621 + 0 + 0.044 + 0.033 + 0 = 0.2045
z = 0.4062*0.3 + 0.4621*0.0 + 0.5129*0.1 + 0.22*0.2 + 0.33*0.3 + 0.44*0.1 = 0.12186 + 0 + 0.05129 + 0.044 + 0.099 + 0.044 = 0.3602
Фінальний output вектор:output = [0.2972, 0.2045, 0.3602]Крок 7: Додавання Residual (залишкового з’єднання)
це підсумовування входу шару з його виходом. Ми складаємо вихід із вхідним вектором, який подавався на вхід блоку (input embedding або вихід попереднього шару).
Це використовується, щоб:
Зберегти вихідну інформацію (градієнти легше передаються назад).
Уникнути “згасання” сигналу крізь безліч шарів.
Полегшити навчання навіть дуже глибоких нейромереж.
Вхідні дані:
Вхідний векторinput = [0.035, 0.207, 0.473]
Вихідний векторoutput = [0.2972, 0.2045, 0.3602]
Обчислення:
residual = output + input = residual = [0.3322, 0.4115, 0.8332]Крок 8: Нормалізація шару (Layer Normalization)
нормалізує значення всередині одного вектора ознак, тобто за розмірністю ознак (feature dimension).
Навіщо це потрібно:
Усуває зміщення й масштабні відмінності між ознаками.
Робить навчання стабільнішим.
Пришвидшує збіжність нейромережі.
Краще працює при маленьких батчах, на відміну від BatchNorm.
З точки зору математики:
Обчислюємо середнє значення (mean):μ = (1/n) * ∑ xₙ
Обчислюємо стандартне відхилення (std):σ = sqrt((1/n) * ∑ (xₙ - μ)^2 + ε)Де ε - маленьке число, щоб уникнути ділення на нуль.
Нормалізуємо кожен елемент:̂xₙ = (xₙ - μ) / σ
Опційно масштабуємо та зміщуємо (навчувані параметри):yₙ = γ * ̂xₙ + βПриклад:
Вхідні дані:
x = [0.5972, 0.3045, 0.5602]
Обчислення:
Обчислимо середнє:μ = (0.5972 + 0.3045 + 0.5602) / 3 = 1.4619 / 3 ≈ 0.4873
Обчислимо стандартне відхилення:σ = sqrt(((0.5972 - 0.4873)^2 + (0.3045 - 0.4873)^2 + (0.5602 - 0.4873)^2) / 3) = sqrt((0.0121 + 0.0334 + 0.0053) / 3) = sqrt(0.0169) ≈ 0.13
Нормалізуємо:̂x₁ = (0.5972 - 0.4873) / 0.13 ≈ 0.845̂x₂ = (0.3045 - 0.4873) / 0.13 ≈ -1.405̂x₃ = (0.5602 - 0.4873) / 0.13 ≈ 0.561
Після округлення та застосування масштабування і зміщення (якщо є):≈ [0.87, -1.13, 0.26]Крок 9: FFN (Feed-Forward Network) і MLP (Multilayer Perceptron)
FFN (Feed-Forward Network):
це компонент трансформера, який обробляє кожне слово (або токен) окремо, без урахування інших токенів.
Він застосовується незалежно до кожного токена після шару уваги і являє собою двошарову нейромережу з нелінійною функцією активації.
Це дозволяє моделі вловлювати складніші залежності.
MLP (Multilayer Perceptron):
це тип нейронної мережі, що складається з кількох шарів повністю пов’язаних нейронів. У контексті LLM і трансформерів MLP часто означає ту саму Feed-Forward Network (FFN).
MLP = загальна назва архітектури,
FFN = окремий випадок MLP, що використовується всередині трансформерів.
З точки зору математики:
x - вхідний вектор розмірності dW1-матриця ваг першого лінійного шару розмірності (d_ff × d)b1-зміщення першого шару розмірності d_ffW2-матриця ваг другого лінійного шару розмірності (d × d_ff)b2-зміщення другого шару розмірності df-функція активації (наприклад, ReLU або GELU)d-розмірність входу/виходуd_ff-розмірність прихованого шару (зазвичай d_ff = 4 × d)
Основна формула FFN:FFN(x) = W2 · f(W1 · x + b1) + b2
Покроковий розклад:
Лінійне перетворення, розмірність z1: (d_ff × 1):z1 = W1 · x + b1
Нелінійна активація, застосовується поелементно, розмірність зберігається:z2 = f(z1)
Друге лінійне перетворення, підсумковий вектор того ж розміру, що й x: (d × 1):y = W2 · z2 + b2Приклад:
Вхідні дані:
Розмірність прихованого шару d = 4,Внутрішня розмірність d_ff = 8,Активація: ReLU,Вхідний вектор:x = [x[0], x[1], x[2], x[3]] = [1.0, -2.0, 0.5, 3.0]
матриця 8x4 W1 =[ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [1, 1, 1, 1], [1, -1, 1, -1], [0.5, 0.5, 0.5, 0.5], [-1, -1, -1, -1]]
Обчислення:
y[0] = 1 * x[0] + 0 * x[1] + 0 * x[2] + 0 * x[3] = 1.0 + 0 + 0 + 0 = 1.0y[1] = 0 * x[0] + 1 * x[1] + 0 * x[2] + 0 * x[3] = 0 - 2.0 + 0 + 0 = -2.0y[2] = 0 * x[0] + 0 * x[1] + 1 * x[2] + 0 * x[3] = 0 + 0 + 0.5 + 0 = 0.5y[3] = 0 * x[0] + 0 * x[1] + 0 * x[2] + 1 * x[3] = 0 + 0 + 0 + 3.0 = 3.0y[4] = 1 * x[0] + 1 * x[1] + 1 * x[2] + 1 * x[3] = 1.0 - 2.0 + 0.5 + 3.0 = 2.5y[5] = 1 * x[0] + (-1) * x[1] + 1 * x[2] + (-1) * x[3] = 1.0 + 2.0 + 0.5 - 3.0 = 0.5y[6] = 0.5 * x[0] + 0.5 * x[1] + 0.5 * x[2] + 0.5 * x[3] = 0.5 - 1.0 + 0.25 + 1.5 = 1.25y[7] = -1 * x[0] + (-1) * x[1] + (-1) * x[2] + (-1) * x[3] = -1.0 + 2.0 - 0.5 - 3.0 = -2.5
Вектор після лінійного шару (до активації):y = [1.0, -2.0, 0.5, 3.0, 2.5, 0.5, 1.25, -2.5]
Застосовуємо ReLU:ReLU(y[i]) = max(0, y[i])
ReLU(y) = [1.0, 0.0, 0.5, 3.0, 2.5, 0.5, 1.25, 0.0]Крок 10: Residual + LayerNorm (другий шар нормалізації)
Після FFN до результату знову додають вхід цього блоку (результат кроку 6) і знову нормалізують вектор — це допомагає зберегти інформацію та стабілізувати розрахунки
Крок 11: Подача виходу блоку в наступний трансформер-блок
Отримавши на виході одного блоку трансформера нормалізований вектор, модель передає його на вхід наступному блоку. Таких блоків може бути десятки — кожен додає дедалі більше «розуміння» контексту
Крок 12: Фінальна LayerNorm нормалізація після останнього блоку
Після останнього шару ще раз застосовують LayerNorm — це заключний штрих перед генерацією логітів, щоб згладити розкид значень
Крок 13: Лінійна проєкція в логіти (Logits Projection) + Softmax
Нормалізований вектор з останнього шару множать на матрицю ембедингів (або окремий лінійний шар), щоб отримати «сирі» оцінки (logits) для кожного токена словника. Потім ці оцінки перетворюють на ймовірності за допомогою Softmax або одразу передають у семплери для відбору токена
Що таке logits:
"сирі" значення, які ще не нормалізовані в імовірності.
Наприклад, якщо словник складається з 50 000 токенів, то logits — це просто 50 000 чисел, одне для кожного токена.
Для кожного токена в послідовності модель сформувала його представлення (hidden state).
Тепер треба перетворити цей hidden state на логіти за словником — оцінку ймовірності появи кожного можливого наступного токена.
Перетворення через Linear Layer:
logits = hidden_state @ Wᵀ + b
hidden_state: останній вектор (або вся послідовність — але частіше цікавить останній токен),Wᵀ: транспонована матриця ембедингів (розмір [vocab_size, hidden_dim]),b: зсув (bias), часто опускається для економії.майже в усіх сучасних LLM застосовується weight tying — тобто Wᵀ береться з того самого шару, що й ембединги на вході. Це економить пам’ять і покращує узагальнювальну здатність моделі.
Крок 14: Вибір наступного токена (sampling)
Отримані logits проходять через ланцюжок семплерів: спочатку застосовуються штрафи за повтори й температура, потім фільтри (top-k, top-p та ін.), а наприкінці — фінальний семплер (міростат, жадібний вибір або розподіл). Саме цей токен додають у контекст, і генерація повторюється з кроку 1 до досягнення кінця або потрібної довжини.
У бекенді LLama.cpp можна складати ланцюжки семплерів, спочатку проміжні, потім ланцюжок має завершуватися фінальним семплером.
Порядок застосування семплерів у llama.cpp:
1. Штрафи за повтори: - repeat_penalty - frequency_penalty - presence_penalty
2. Масштабування логітів: - temperature
3. Фільтрація токенів: - top_k - top_p - min_p - typical_p - tfs_z
4. Граматичні обмеження: - grammar
5. Фінальний вибір токена: - mirostat (v1 або v2) або - greedy / random samplingПриклад з LLama.cpp (для розробників):
// підготовка параметрів ланцюжкаstruct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();llama_sampler * smpl = llama_sampler_chain_init(sparams);
// проміжні семплери:llama_sampler_chain_add(smpl, llama_sampler_init_top_k (50)); // Top-Kllama_sampler_chain_add(smpl, llama_sampler_init_top_p (0.9f, 1)); // Top-P (nucleus)llama_sampler_chain_add(smpl, llama_sampler_init_min_p (0.8f, 1)); // Min-Pllama_sampler_chain_add(smpl, llama_sampler_init_typical (0.95f, 1)); // Locally Typicalllama_sampler_chain_add(smpl, llama_sampler_init_temp (0.7f)); // Temperaturellama_sampler_chain_add(smpl, llama_sampler_init_temp_ext (0.7f, 0.1f, 1.5f)); // Extended templlama_sampler_chain_add(smpl, llama_sampler_init_xtc (0.9f, 1.0f, 1, LLAMA_DEFAULT_SEED)); // XTCllama_sampler_chain_add(smpl, llama_sampler_init_top_n_sigma(1.5f)); // Top-nσ
llama_sampler_chain_add(smpl, llama_sampler_init_grammar(vocab, grammar, "root"));llama_sampler_chain_add(smpl,llama_sampler_init_grammar_lazy_patterns(vocab, grammar, "root", patterns, 2, tokens, N));
// фінальний семплер, лише 1 варіант із:llama_sampler_chain_add(smpl, llama_sampler_init_mirostat(32000, LLAMA_DEFAULT_SEED, 5.0f, 0.1f, 100)); // Mirostat V1llama_sampler_chain_add(smpl, llama_sampler_init_mirostat_v2(LLAMA_DEFAULT_SEED, 5.0f, 0.1f)); // Mirostat V2llama_sampler_chain_add(smpl, llama_sampler_init_greedy()); // Greedyllama_sampler_chain_add(smpl, llama_sampler_init_dist(LLAMA_DEFAULT_SEED)); // Dist
// після генерації не забути звільнити ланцюжок:llama_sampler_free(smpl);Приклад сторінки налаштування семплерів із застосунку https://github.com/a-ghorbani/pocketpal-ai


Проміжні семплери
Temperature (temp)
керує ступенем випадковості при виборі наступного токена з розподілу ймовірностей, обчисленого моделлю. Температура звужує або розширює "лійку вибору" наступного слова. Чим вона нижча — тим вужча лійка, чим вища — тим ширша.
Після того як модель передбачила логіти (сирі значення ймовірностей) для всіх можливих токенів, вони проходять через softmax-функцію. Температура впливає на цю функцію:
P(token) = softmax(logits / temperature)Ефект параметра temperature:
T = 1 логіти залишаються як є.
T < 1 (наприклад, 0.7) модель посилює відмінності між токенами — ймовірні токени стають ще ймовірнішими. Поведінка стає передбачуванішою.
T > 1 (наприклад, 1.5) відмінності між токенами згладжуються — зростає шанс вибрати менш імовірний токен. Поведінка стає різноманітнішою й ризикованішою.
T → 0 softmax перетворюється на argmax, і завжди обирається один найімовірніший токен.
Вихідні логіти:Token A: 3.0Token B: 2.5Token C: 1.0
Після softmax без змін (temp = 1.0):A: 64%B: 24%C: 12%
Зі зниженою температурою (temp = 0.5):A: 85%B: 13%C: 2%
З підвищеною температурою (temp = 1.5):A: 47%B: 31%C: 22%Top-k
модель обирає наступний токен тільки з k найімовірніших. Усі інші токени відкидаються, незалежно від їхньої абсолютної ймовірності.
є 6 токенів з такими ймовірностями:A: 0.35B: 0.30C: 0.15D: 0.10E: 0.06F: 0.04
Якщо top_k = 3, то залишимо лише:A: 0.35B: 0.30C: 0.15Top-p (nucleus sampling)
обирається мінімальна множина токенів з найбільшими ймовірностями, сума яких перевищує заданий поріг p. Решта токенів повністю відсікаються, навіть якщо вони мали високий ранг.
модель видала ймовірності токенів:A: 0.50B: 0.25C: 0.15D: 0.08E: 0.02
При min_p = 0.1, залишаються лише:A, B, CD і E будуть відкинуті, навіть якщо загальна сума тепер менша за 1.0 — потім ті, що залишилися, нормалізуються.Min-p
відсікаються всі токени з імовірністю нижче заданого порога p. Він жорстко обмежує вибір, виключаючи надто малоймовірні токени незалежно від їхнього рангу або загальної суми ймовірностей, як у top-p.
Typical sampling
обирає токени, близькі до «типового» рівня несподіваності (surprisal), відсікаючи надто передбачувані й надто рідкісні. На відміну від top-k і top-p, він орієнтується на медіанне значення surprisal, а не на ймовірність токенів.
Repetition penalties, Frequency penalty, Presence penalty
знижує шанси повторного вибору вже згенерованих токенів, зменшуючи їхні логіти. Це допомагає уникнути нав’язливих повторів у тексті й робить вивід різноманітнішим.
Logit bias
метод, що дозволяє вручну змінити ймовірність окремих токенів перед застосуванням softmax, додаючи або віднімаючи значення з їхніх логітів. Це дає точковий контроль: можна, наприклад, змусити модель уникати певних слів або, навпаки, частіше їх обирати.
Tail Free Sampling (TFS)
відкидає довгий «хвіст» розподілу, регулюючи щільність імовірностей.
та інші
Фінальні семплери
Greedy
найпростіший спосіб генерації, при якому модель завжди обирає токен з найвищою ймовірністю (максимальний логіт). Він швидкий і детермінований, але часто призводить до одноманітного й передбачуваного тексту.
Random
вибір наступного токена випадково, пропорційно його ймовірності після всіх застосованих семплерів (top-k, top-p тощо). Це протилежність жадібного вибору (greedy), де завжди береться токен з максимальною ймовірністю.
Dist
знижує ймовірність токенів, що утворюють уже наявні n-грами, щоб зменшити повторюваність тексту. Він динамічно штрафує повтори, роблячи генерацію різноманітнішою.
Mirostat v1
підтримує заданий рівень «здивування» (перплексії), динамічно коригуючи вибір слів. Він допомагає уникнути надмірних повторів (пастка нудьги) і незв’язності (пастка плутанини), забезпечуючи збалансовану та якісну генерацію тексту.
Mirostat v2
підтримує заданий рівень несподіваності (перплексії) за допомогою точнішого керування, ніж Mirostat v1. Він використовує розширений механізм зворотного зв’язку, що дозволяє динамічно коригувати вибір слів для досягнення стабільної якості тексту.
Grammar
використовує правила граматики (зазвичай у вигляді CFG) для жорсткого обмеження допустимих токенів на кожному кроці генерації. Замість зміни логітів, він просто забороняє всі токени, що не відповідають поточному допустимому стану граматики, забезпечуючи строго структурований вивід.
та інші
Крок 15: Ітерація генерації
Після того як модель обрала наступний токен, він додається в кінець вхідної послідовності.
Контекст розширюється: тепер як “вихідний” текст для токенізації беруться всі попередні токени плюс новозгенерований.
Потім алгоритм повертається до кроку 1 (токенізація), і процес повторюється доти, доки не будуть виконані умови припинення.
Такий цикл дозволяє по одному токену будувати всю вихідну послідовність.
Умови завершення
Генерація завершується, коли модель видає спеціальний токен кінця речення (EOS) або коли досягається заздалегідь задана максимальна довжина послідовності.
Це обмеження запобігає нескінченним або надто довгим відповідям.
У деяких реалізаціях додають ще й правило завершення за ймовірнісним порогом: якщо максимальна ймовірність наступного токена нижча за задане значення, генерація теж зупиняється.
Крок 16: Перетворення токенів на слова
Після того як модель згенерувала потрібну кількість токенів, їх перетворюють назад на текст — це називається детокенізація. Токенізатор бере послідовність чисел (токенів) і переводить їх у слова та розділові знаки. Зазвичай це роблять уже після повної генерації або по ходу, якщо потрібно отримувати текст «на льоту».
TODO: у статті багато неточностей, вони будуть виправлятися в міру наявності вільного часу й бажання
Додаткова інформація
Способи запуску (бекенди) для LLM моделей
ONNX Runtime:
Кросплатформний рушій для виконання моделей у форматі ONNX.
Формат файлу: .onnx
TensorFlow (Inference API):
Стандартний рушій для моделей TensorFlow, підтримує SavedModel і заморожений граф.
Формати: папка SavedModel (saved_model.pb + variables/), єдиний файл .pb
TensorFlow Lite:
Легкий рушій для мобільних і вбудовуваних пристроїв, оптимізований за розміром і швидкістю.
Формат: .tflite
PyTorch (TorchScript):
Дозволяє серіалізувати й запускати моделі без залежності від Python, з оптимізаціями JIT.
Формати: .pt, .pth
NVIDIA TensorRT:
Апаратно-прискорений рушій для GPU NVIDIA, компілює моделі (зазвичай з ONNX) під конкретну карту.
Формат: скомпільований рушій .engine (або план UFF/ONNX → .engine)
Intel OpenVINO:
Оптимізує й пришвидшує мережі на CPU та Intel GPU/VPU, конвертує моделі у IR-формат.
Формати: .xml (структура) + .bin (ваги)
Apple Core ML:
Фреймворк для запуску моделей на iOS/macOS, інтегрується з Xcode і прискорюється через Core ML Runtime.
Формат: .mlmodel
Microsoft ML.NET:
.NET-рушій для інференсу на CPU, підходить для C# і F#.
Формат: архів моделі .zip
Apache TVM:
Компілює й оптимізує моделі під різноманітне залізо, створює нативні бібліотеки.
Формати: серіалізований Relay-модуль або скомпільована бібліотека (.so, .dll)
Alibaba MNN:
Мобільний нейронний рушій з широкими оптимізаціями для ARM, підтримує серверний запуск.
Формат: .mnn
Tencent NCNN:
Компактний рушій для мобільних CPU/GPU, без сторонніх залежностей.
Формати: .param (структура) + .bin (ваги)
OpenCV DNN:
Модуль комп’ютерного зору з підтримкою різних форматів (ONNX, Caffe, TensorFlow, Darknet).
Формати: залежить від вихідного — .onnx, .pb, .caffemodel + .prototxt, .weights
Unity Barracuda:
Рушій для запуску нейромереж в іграх Unity, підтримує ONNX-моделі.
Формат: .nn
MLC LLM:
ML-компілятор і рушій для LLM, компілює ваги у свій IR і шардує їх.
Формат: каталог …-MLC з .bin-шардами та JSON-конфігами (наприклад, mlc-chat-config.json)
MediaPipe:
Фреймворк для створення мультимодальних пайплайнів (детекція, сегментація та ін.), спирається на TFLite.
Формати: .tflite (модель) + .pbtxt (граф)
llama.cpp:
C++-рушій для LLaMA-подібних моделей з підтримкою квантування на CPU/GPU.
Формати: GGML-моделі (.bin, .gguf)
GGML:
Бібліотека для ефективного інференсу моделей (основа llama.cpp та ін.).
Формати: .gguf, .bin
NeuralMagic DeepSparse:
Оптимізований рушій для розріджених нейромереж на CPU.
Формат: .onnx
AWS Neuron SDK:
Рушій для прискорення інференсу на AWS Inferentia, компілює моделі під Neuron.
Формати: Neuron-артефакти (.nef або бібліотека)
Glow:
Компілятор і рантайм від Facebook для нейромереж, вхідний формат ONNX.
Формати: проміжний файл .bc або скомпільована бібліотека .so
Apache MXNet:
Фреймворк із власним рантаймом для CPU/GPU, підтримує Docker-розгортання.
Формати: .params (ваги) + .json (мережа)
Caffe:
Класичний рушій для CV-мереж, часто використовується в дослідженнях.
Формати: .caffemodel (ваги) + .prototxt (опис мережі)
Застосунки для запуску LLM моделей
LM Studio:
Десктоп-застосунок для Windows і macOS з GUI для запуску локальних LLM (GPT-подібних).
Формати моделей: .gguf, .bin
Ollama:
Легкий CLI/GUI-клієнт для Windows і macOS, працює «з коробки» з LLM.
Формати моделей: .gguf, .bin
Automatic1111 Stable Diffusion WebUI:
Найпопулярніший локальний вебінтерфейс для генерації зображень на Windows/macOS/Linux.
Формати моделей: .ckpt, .safetensors
DiffusionBee:
Настільний застосунок для macOS (є бета-збірки для Windows), «все в одному» для Stable Diffusion.
Формати моделей: .ckpt, .safetensors
InvokeAI:
Кросплатформний пакет з CLI і WebUI для генерації зображень на базі Stable Diffusion.
Формати моделей: .ckpt, .safetensors
llama.cpp (prebuilt binaries):
Готові виконувані файли для Windows/macOS/Linux, дозволяють запускати LLaMA-подібні моделі без встановлення залежностей.
Формати моделей: .bin, .gguf
Формат зберігання LLM моделей
.pt / .pth
Використовуються в PyTorch для збереження навчених моделей
Зберігають ваги й структуру моделі (або тільки ваги).
Ґрунтуються на серіалізації Python (pickle), що робить їх не дуже безпечними.
Підходять для навчання й донавчання.
Не оптимізовані для мобільного виводу або зовнішнього інференсу
Можуть займати багато пам’яті (FP32).
.safetensors
Альтернатива .pt, безпечний і швидкий формат для PyTorch.
Не використовує pickle, а отже безпечний при завантаженні.
Підтримує паралельне завантаження, що пришвидшує роботу.
Тільки для зберігання ваг (структура — окремо).
Підходить для інференсу й донавчання.
Підтримується HuggingFace, PyTorch, JAX.
.bin
Універсальний або частковий бінарний формат для ваг моделей, особливо в HuggingFace і старому GGML.
Може бути неформалізований (структура залежить від фреймворка).
Може містити повні ваги або квантовані дані.
Підходить для завантаження в кастомні рушії (наприклад, llama.cpp).
Вимагає точного знання, як інтерпретувати вміст.
Часто використовується в старих проєктах або кастомних пайплайнах.
.gguf
Новий стандарт від GGML для запуску LLM у llama.cpp, Ollama, MLC LLM.
Включає все в одному файлі: ваги, словник, токенізатор, параметри моделі.
Підтримує різні типи квантування (Q2_K, Q4_0, Q8_1 тощо).
Добре стиснутий і оптимізований під запуск на CPU, GPU, Android.
Швидко завантажується й легко обробляється C++ кодом.
Основний вибір для локального запуску LLM (Gemma, LLaMA, Mistral).
.onnx
Крос-фреймворк формат для запуску моделі в різних середовищах (Windows, Web, C#, Java та ін.).
Стандартизований, підтримується багатьма фреймворками (PyTorch, TF, Keras).
Спрощує перенесення моделей між платформами.
Підходить для інференсу (не для навчання).
Оптимізується засобами ONNX Runtime (стиснення, квантування).
Чудовий вибір для запуску моделей у embedded-середовищах і .NET.
.tflite
Формат для запуску моделей TensorFlow на мобільних пристроях (Android, iOS).
Дуже компактний і швидкий.
Підтримує INT8 і FP16 квантування.
Працює з TensorFlow Lite Interpreter, легко вбудовується в Android.
Не можна навчати або донавчати — тільки запуск.
Підтримує базові CNN, RNN, Seq2Seq, але не LLM.
.mlc
Формат для MLC LLM — запуск LLM на Android/iOS/GPU через компіляцію.
Модель попередньо компілюється в ефективний байткод.
Підтримує Vulkan/Metal/OpenCL для прискорення.
Використовується для Gemma, Mistral, LLaMA на телефоні.
Вимагає складної підготовки (TVM, скрипти, налаштування).
Чудово підходить для мобільних застосунків без серверів.