<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maksim Sekretov</title>
    <description>The latest articles on DEV Community by Maksim Sekretov (@maktordev).</description>
    <link>https://dev.to/maktordev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043726%2F157673df-1a18-4dce-a1a7-dd03b959d5db.jpg</url>
      <title>DEV Community: Maksim Sekretov</title>
      <link>https://dev.to/maktordev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maktordev"/>
    <language>en</language>
    <item>
      <title>ML без магии: одна Tiny Language Model, одна команда Node.js и все веса на виду</title>
      <dc:creator>Maksim Sekretov</dc:creator>
      <pubDate>Sun, 26 Jul 2026 13:22:07 +0000</pubDate>
      <link>https://dev.to/maktordev/ml-biez-maghii-odna-tiny-language-model-odna-komanda-nodejs-i-vsie-viesa-na-vidu-509e</link>
      <guid>https://dev.to/maktordev/ml-biez-maghii-odna-tiny-language-model-odna-komanda-nodejs-i-vsie-viesa-na-vidu-509e</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/maktordev/ml-without-magic-building-a-tiny-language-model-in-pure-nodejs-and-watching-every-weight-change-5dfh"&gt;English version&lt;/a&gt; | &lt;strong&gt;Русская версия&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tokenization → embeddings → causal Transformer → LM head → softmax → loss → backpropagation. Без TensorFlow, PyTorch и скрытого autograd.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Репозиторий: &lt;strong&gt;&lt;a href="https://github.com/sekretov/tiny-language-model-neuro-js" rel="noopener noreferrer"&gt;tiny-language-model-neuro-js&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Большинство объяснений language model показывает правильные формулы, но прячет путь между ними внутри фреймворка. Мне хотелось обратного: один маленький сценарий, в котором виден каждый скаляр, а терминал прямо показывает неправильные ответы до обучения и правильные после него.&lt;/p&gt;

&lt;p&gt;Теперь в проекте одна команда:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node src/train.js &lt;span class="nt"&gt;--generalize&lt;/span&gt; &lt;span class="nt"&gt;--adaptive-teach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Нужен Node.js 18.19+. Зависимостей нет.&lt;/p&gt;

&lt;p&gt;Реальный фрагмент из &lt;code&gt;logs/training-log.txt&lt;/code&gt;: матрицы AFTER и DELTA одного FFN-слоя:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7v2i0weqogctm6oy7en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7v2i0weqogctm6oy7en.png" alt="Матрицы весов AFTER и DELTA из logs/training-log.txt" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Сначала результат
&lt;/h2&gt;

&lt;p&gt;Модель получает вопросы сразу после случайной инициализации:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE TRAINING — random, usually wrong answers
&amp;gt; can human read ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: human can read.  [WRONG]

&amp;gt; can fish swim ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: fish can swim.   [WRONG]

&amp;gt; can cat read ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: cat cannot read. [WRONG]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;После pre-training, SFT и adaptive SFT та же модель отвечает:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FINAL ANSWERS AFTER ADAPTIVE SFT
&amp;gt; can human read ?
  model:    human can read.  [CORRECT]
&amp;gt; can fish swim ?
  model:    fish can swim.   [CORRECT]
&amp;gt; can bird fly ?
  model:    bird can fly.    [CORRECT]
&amp;gt; can cat read ?
  model:    cat cannot read. [CORRECT]

Rehearsal controls preserved: 14/14.
Stable criterion reached 11 times in a row.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Начальный текст меняется из-за случайных весов. Итоговый критерий строгий: все ответы должны быть правильными, вероятность каждого target-токена должна составлять минимум 95%, а полная проверка обязана пройти более десяти раз подряд.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что осталось после удаления лишних режимов
&lt;/h2&gt;

&lt;p&gt;Раньше код содержал несколько режимов отладки и обучения. Они помогали во время экспериментов, но закрывали главную мысль. В итоговой версии остался один pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;текст → word tokenization → token IDs
      → token + position embeddings
      → два causal Transformer-блока
         → multi-head self-attention
         → FFN с двумя скрытыми слоями
      → LM head → softmax → вероятности следующего токена
      → cross-entropy → backpropagation → Adam
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Теперь &lt;code&gt;train.js&lt;/code&gt; читается как одна история, а не как маленький CLI-фреймворк.&lt;/p&gt;

&lt;h2&gt;
  
  
  Скаляр создаёт вычислительный граф
&lt;/h2&gt;

&lt;p&gt;Каждое число, участвующее в обучении, является объектом &lt;code&gt;Value&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;backward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_backward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Для умножения:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y = a × b
dy/da = b
dy/db = a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Операция запоминает локальные производные. &lt;code&gt;backward()&lt;/code&gt; строит топологический порядок графа и применяет chain rule от итогового loss назад к embeddings и весам.&lt;/p&gt;

&lt;h2&gt;
  
  
  Нейрон — буквально объект
&lt;/h2&gt;

&lt;p&gt;Формула нейрона не спрятана за tensor API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output = activation(sum(input[i] × weight[i]) + bias)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Реализация повторяет формулу:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useBias&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;relu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Linear&lt;/code&gt; — просто массив нейронов, получающих один вход. Это медленнее матричного умножения, зато каждый вычислительный шаг можно увидеть.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embeddings и порядок слов
&lt;/h2&gt;

&lt;p&gt;Каждый token ID выбирает обучаемый вектор:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token representation = tokenEmbedding[id] + positionEmbedding[position]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Сначала embeddings содержат случайные числа. Полезные связи появляются только потому, что градиенты многократно изменяют эти числа в учебных контекстах. У &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt; или &lt;code&gt;cannot&lt;/code&gt; нет вручную заданного свойства &lt;code&gt;meaning&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-attention без сокращений
&lt;/h2&gt;

&lt;p&gt;Для каждого токена:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q = X × Wq
K = X × Wk
V = X × Wv

score = dot(Q, K) / sqrt(headSize)
attention = softmax(score)
output = attention × V
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Цикл работает только при &lt;code&gt;past &amp;lt;= position&lt;/code&gt;. Это causal mask: модель видит текущий токен и историю, но никогда не получает будущий target.&lt;/p&gt;

&lt;p&gt;После attention каждый токен проходит через FFN с двумя скрытыми слоями:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dModel → hidden ReLU → hidden ReLU → dModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LayerNorm и residual paths окружают attention и FFN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Полный шаг обучения
&lt;/h2&gt;

&lt;p&gt;Самый важный код проекта занимает несколько строк:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;learnOneToken&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeroGrad&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loss — обычная next-token cross-entropy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loss = -log(P(target | предыдущие токены))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если вероятность правильного токена мала, loss велик. Backpropagation вычисляет &lt;code&gt;dLoss/dWeight&lt;/code&gt;, Adam изменяет параметры, а следующий forward pass возвращает уже другое распределение.&lt;/p&gt;

&lt;h2&gt;
  
  
  Этап 1: pre-training
&lt;/h2&gt;

&lt;p&gt;Маленький мир содержит 14 связей способностей:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;human can read .
fish can swim .
bird can fly .
dog cannot read .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Связь &lt;code&gt;cat + read&lt;/code&gt; намеренно отсутствует. Pre-training выбирает позиции из этого текста и учится предсказывать следующий токен.&lt;/p&gt;

&lt;h2&gt;
  
  
  Этап 2: SFT
&lt;/h2&gt;

&lt;p&gt;Те же связи превращаются в 42 пары prompt-answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can fish swim ?
is fish able to swim ?
does fish know how to swim ?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В SFT loss участвуют только токены ответа. На каждой эпохе реализация проходит каждую пару и каждую позицию ответа, поэтому цикл остаётся детерминированным и понятным.&lt;/p&gt;

&lt;h2&gt;
  
  
  Этап 3: adaptive SFT
&lt;/h2&gt;

&lt;p&gt;Недостающий ответ представлен только target-токенами:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cannot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Эти targets получают шесть вариантов вопроса. Это прямое supervision: модель не открыла зоологический факт самостоятельно. Учитель передал факт через loss, а backpropagation распределил информацию по embeddings, attention, FFN, LayerNorm и LM head.&lt;/p&gt;

&lt;p&gt;Почему не остановиться после одного правильного ответа? Потому что одна генерация может быть неустойчивой. Цикл продолжается, пока вероятность каждого target-токена не превысит 95%, а вся проверка не завершится успешно 11 раз подряд.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catastrophic forgetting и rehearsal
&lt;/h2&gt;

&lt;p&gt;Первая реализация обучала только шесть новых вопросов про кошку. Новый ответ она запомнила, но прежнее поведение разрушилось:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can human read ? → cat cannot read.
can fish swim ?  → cat cannot read.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это catastrophic forgetting в миниатюре. Исправление — rehearsal: adaptive-эпохи повторяют ещё и 14 старых примеров &lt;code&gt;can ... ?&lt;/code&gt;. Финальный критерий проверяет одновременно новые и старые ответы, поэтому модель не может закончить обучение, просто заменив всё одним ответом.&lt;/p&gt;

&lt;h2&gt;
  
  
  Лог создаётся всегда
&lt;/h2&gt;

&lt;p&gt;Команда автоматически записывает:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logs/training-log.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это последовательная ASCII-схема, а не сырой JSON-дамп. Сначала в ней идут все&lt;br&gt;
события forward/loss/backward/update, затем полные матрицы в трёх контрольных&lt;br&gt;
точках:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;начальные случайные матрицы
        |
        v
матрицы после pre-training + SFT
        |
        v
финальные матрицы после adaptive SFT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Для каждого перехода лог печатает AFTER-матрицу и точную DELTA-матрицу.&lt;br&gt;
Строки Linear-слоёв подписаны как &lt;code&gt;neuron[n]&lt;/code&gt;, столбцы — как &lt;code&gt;weight[n]&lt;/code&gt;,&lt;br&gt;
а bias показан рядом со своим нейроном. Отдельно отмечается самое сильное&lt;br&gt;
изменение в формате &lt;code&gt;слой / нейрон / вес: before -&amp;gt; after -&amp;gt; delta&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Насколько это близко к production LLM
&lt;/h2&gt;

&lt;p&gt;Архитектура и правило обучения настоящие, масштаб намеренно маленький.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Эта модель&lt;/th&gt;
&lt;th&gt;Production model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24 word-токена&lt;/td&gt;
&lt;td&gt;Большой subword/byte-level словарь&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 160 параметров&lt;/td&gt;
&lt;td&gt;Миллионы или миллиарды&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Два Transformer-блока&lt;/td&gt;
&lt;td&gt;Десятки или сотни&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Скалярный JavaScript-граф&lt;/td&gt;
&lt;td&gt;Батчевый тензорный граф на ускорителях&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Маленький структурированный корпус&lt;/td&gt;
&lt;td&gt;Огромные подготовленные datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Узкое обученное поведение&lt;/td&gt;
&lt;td&gt;Широкий язык и reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Проект не конкурирует с GPT. Это causal language model, уменьшенная до размера, при котором весь путь помещается в одном репозитории и одной мысленной модели:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;токен → embedding → attention → FFN → probability
      → loss → gradient → новый вес → изменившийся ответ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Именно этот путь здесь важен. Когда он виден целиком, фреймворки перестают казаться магией: они выполняют те же классы операций в масштабе и со скоростью, от которых эта скалярная реализация намеренно отказалась.&lt;/p&gt;

&lt;p&gt;Репозиторий: &lt;strong&gt;&lt;a href="https://github.com/sekretov/tiny-language-model-neuro-js" rel="noopener noreferrer"&gt;tiny-language-model-neuro-js&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Автор: &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/maksim-sekretov-maktordev/" rel="noopener noreferrer"&gt;Максим Секретов&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>javascript</category>
      <category>russian</category>
      <category>node</category>
    </item>
    <item>
      <title>ML Without Magic: Building a Tiny Language Model in Pure Node.js and Watching Every Weight Change</title>
      <dc:creator>Maksim Sekretov</dc:creator>
      <pubDate>Sat, 25 Jul 2026 09:34:59 +0000</pubDate>
      <link>https://dev.to/maktordev/ml-without-magic-building-a-tiny-language-model-in-pure-nodejs-and-watching-every-weight-change-5dfh</link>
      <guid>https://dev.to/maktordev/ml-without-magic-building-a-tiny-language-model-in-pure-nodejs-and-watching-every-weight-change-5dfh</guid>
      <description>&lt;p&gt;English Version | &lt;a href="https://dev.to/maktordev/ml-biez-maghii-odna-tiny-language-model-odna-komanda-nodejs-i-vsie-viesa-na-vidu-509e"&gt;Русская версия&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tokenization → embeddings → causal Transformer → LM head → softmax → loss → backpropagation. No TensorFlow, no PyTorch, and no hidden autograd.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repository: &lt;strong&gt;&lt;a href="https://github.com/sekretov/tiny-language-model-neuro-js" rel="noopener noreferrer"&gt;tiny-language-model-neuro-js&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most explanations of language models present correct formulas but hide the path between them inside a framework. I wanted the opposite: one small scenario where every scalar is visible and where the terminal clearly shows incorrect answers before learning and correct answers after it.&lt;/p&gt;

&lt;p&gt;The project now has one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node src/train.js &lt;span class="nt"&gt;--generalize&lt;/span&gt; &lt;span class="nt"&gt;--adaptive-teach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It requires Node.js 18.19+ and has no dependencies.&lt;/p&gt;

&lt;p&gt;A real excerpt from &lt;code&gt;logs/training-log.txt&lt;/code&gt;, showing the AFTER and DELTA matrices for one FFN layer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7v2i0weqogctm6oy7en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7v2i0weqogctm6oy7en.png" alt="AFTER and DELTA weight matrices from logs/training-log.txt" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result first
&lt;/h2&gt;

&lt;p&gt;The model is queried immediately after random initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE TRAINING — random, usually wrong answers
&amp;gt; can human read ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: human can read.  [WRONG]

&amp;gt; can fish swim ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: fish can swim.   [WRONG]

&amp;gt; can cat read ?
  model:    ? &amp;lt;unk&amp;gt; ...
  expected: cat cannot read. [WRONG]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After pre-training, SFT, and adaptive SFT, the same model produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FINAL ANSWERS AFTER ADAPTIVE SFT
&amp;gt; can human read ?
  model:    human can read.  [CORRECT]
&amp;gt; can fish swim ?
  model:    fish can swim.   [CORRECT]
&amp;gt; can bird fly ?
  model:    bird can fly.    [CORRECT]
&amp;gt; can cat read ?
  model:    cat cannot read. [CORRECT]

Rehearsal controls preserved: 14/14.
Stable criterion reached 11 times in a row.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initial text varies because initialization is random. The final acceptance criterion does not: all answers must be correct, every target token must have at least 95% probability, and the complete check must pass more than ten times consecutively.&lt;/p&gt;

&lt;h2&gt;
  
  
  What remains after removing the extra modes
&lt;/h2&gt;

&lt;p&gt;The code previously contained several debug and training modes. They were useful while experimenting but obscured the main idea. The final version keeps one educational pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text → word tokenization → token IDs
     → token + position embeddings
     → two causal Transformer blocks
        → multi-head self-attention
        → two-hidden-layer FFN
     → LM head → softmax → next-token probabilities
     → cross-entropy → backpropagation → Adam
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;train.js&lt;/code&gt; now reads as one story rather than a command-line framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  A scalar builds the computation graph
&lt;/h2&gt;

&lt;p&gt;Every number participating in learning is a &lt;code&gt;Value&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;backward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_backward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multiplication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y = a × b
dy/da = b
dy/db = a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operation stores these local derivatives. &lt;code&gt;backward()&lt;/code&gt; sorts the graph topologically and applies the chain rule from the final loss back to embeddings and weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  A neuron is literally an object
&lt;/h2&gt;

&lt;p&gt;The neuron formula is not hidden behind a tensor API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output = activation(sum(input[i] × weight[i]) + bias)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its implementation follows the formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useBias&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bias&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;relu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;Linear&lt;/code&gt; layer is just an array of neurons receiving the same input. This is slower than matrix multiplication but far easier to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embeddings and order
&lt;/h2&gt;

&lt;p&gt;Each token ID selects one trainable vector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token representation = tokenEmbedding[id] + positionEmbedding[position]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Embeddings contain random values initially. They acquire useful relations only because gradients repeatedly change them in training contexts. No &lt;code&gt;meaning&lt;/code&gt; property is assigned to &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, or &lt;code&gt;cannot&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-attention without shorthand
&lt;/h2&gt;

&lt;p&gt;For every token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q = X × Wq
K = X × Wk
V = X × Wv

score = dot(Q, K) / sqrt(headSize)
attention = softmax(score)
output = attention × V
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation loops only while &lt;code&gt;past &amp;lt;= position&lt;/code&gt;. That is the causal mask: the model can attend to the current token and its history but never to a future target.&lt;/p&gt;

&lt;p&gt;After attention, every token passes through a two-hidden-layer feed-forward network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dModel → hidden ReLU → hidden ReLU → dModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LayerNorm and residual paths preserve stable information flow around attention and FFN.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete learning step
&lt;/h2&gt;

&lt;p&gt;The most important code in the project is only a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;learnOneToken&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zeroGrad&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loss is ordinary next-token cross-entropy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loss = -log(P(target | previous tokens))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the correct token has low probability, loss is large. Backpropagation computes &lt;code&gt;dLoss/dWeight&lt;/code&gt;; Adam changes each parameter; the next forward pass gives a different distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: pre-training
&lt;/h2&gt;

&lt;p&gt;The tiny world contains 14 ability relations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;human can read .
fish can swim .
bird can fly .
dog cannot read .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cat + read&lt;/code&gt; relation is missing deliberately. Pre-training samples positions from this text and learns ordinary next-token prediction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: SFT
&lt;/h2&gt;

&lt;p&gt;The same relations are converted into 42 prompt-answer examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can fish swim ?
is fish able to swim ?
does fish know how to swim ?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only answer tokens contribute to SFT loss. The implementation visits every pair and every answer position on each epoch, making the training loop deterministic and readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: adaptive SFT
&lt;/h2&gt;

&lt;p&gt;The missing answer is represented only by target tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cannot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six question variants receive those targets. This is direct supervision: the model did not discover a zoological fact on its own. The teacher introduced the fact through loss, and backpropagation distributed that information across embeddings, attention, FFN, LayerNorm, and the LM head.&lt;/p&gt;

&lt;p&gt;Why not stop after one correct answer? Because one generation can be fragile. The loop continues until every target token exceeds 95% probability and the whole evaluation succeeds 11 times in a row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catastrophic forgetting and rehearsal
&lt;/h2&gt;

&lt;p&gt;An early implementation trained only the six new cat prompts. It successfully learned the new answer and destroyed old behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can human read ? → cat cannot read.
can fish swim ?  → cat cannot read.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is catastrophic forgetting in miniature. The fix is rehearsal: adaptive epochs also repeat the 14 older &lt;code&gt;can ... ?&lt;/code&gt; examples. The final criterion evaluates both new and old examples, so training cannot finish by overwriting everything with one response.&lt;/p&gt;

&lt;h2&gt;
  
  
  The log is always written
&lt;/h2&gt;

&lt;p&gt;The command automatically creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logs/training-log.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a sequential ASCII diagram rather than a raw JSON dump. It includes every&lt;br&gt;
forward/loss/backward/update event, followed by the complete matrices at three&lt;br&gt;
checkpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial random matrices
        |
        v
matrices after pre-training + SFT
        |
        v
final matrices after adaptive SFT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every transition, the log prints the AFTER matrix and its exact DELTA matrix.&lt;br&gt;
Linear rows are named &lt;code&gt;neuron[n]&lt;/code&gt;, columns are named &lt;code&gt;weight[n]&lt;/code&gt;, and biases&lt;br&gt;
are shown beside their neuron. It also points out the largest concrete change as&lt;br&gt;
&lt;code&gt;layer / neuron / weight: before -&amp;gt; after -&amp;gt; delta&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How close is it to a production LLM?
&lt;/h2&gt;

&lt;p&gt;The architecture and learning rule are real; the scale is intentionally tiny.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;This model&lt;/th&gt;
&lt;th&gt;Production model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24 word tokens&lt;/td&gt;
&lt;td&gt;Large subword/byte vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,160 parameters&lt;/td&gt;
&lt;td&gt;Millions or billions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two Transformer blocks&lt;/td&gt;
&lt;td&gt;Tens or hundreds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalar JavaScript graph&lt;/td&gt;
&lt;td&gt;Batched tensor graph on accelerators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small structured corpus&lt;/td&gt;
&lt;td&gt;Massive curated datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrow trained behavior&lt;/td&gt;
&lt;td&gt;Broad language and reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The project is not a GPT competitor. It is a causal language model reduced until the complete path fits in one repository and one mental model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token → embedding → attention → FFN → probability
      → loss → gradient → updated weight → changed answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That path is the point. Once it is visible, frameworks stop looking magical: they execute the same classes of operations at a scale and speed this scalar implementation deliberately avoids.&lt;/p&gt;

&lt;p&gt;Repository: &lt;strong&gt;&lt;a href="https://github.com/sekretov/tiny-language-model-neuro-js" rel="noopener noreferrer"&gt;tiny-language-model-neuro-js&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Author: &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/maksim-sekretov-maktordev/" rel="noopener noreferrer"&gt;Maksim Sekretov&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
: &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>machinelearning</category>
      <category>node</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
