<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Six Fingers]]></title><description><![CDATA[Experiments with AI, machine learning, NLP, and linguistics. Exploring language models, neural networks, emerging technologies, and the latest developments in A]]></description><link>https://sixfingers.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:27:17 GMT</lastBuildDate><atom:link href="https://sixfingers.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Build a Large Language Model from Scratch? Toktumi 3.0 – Part 3 (The Final Part)]]></title><description><![CDATA[Toktumi 3.0: fine-tuning Polish GPT-2 on 6.19M synthetic Q&A, instruction and dialogue pairs. Early stopping and 29-checkpoint evaluation.]]></description><link>https://sixfingers.hashnode.dev/toktumi-3-en</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-3-en</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[nlp]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[fine tuning]]></category><category><![CDATA[synthetic data]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 21:14:02 GMT</pubDate><content:encoded><![CDATA[<h1><strong>CHAPTER III. Toktumi 3.0. Third Training Run.</strong></h1>
<h3>1. The Goal of Further Training</h3>
<p>Toktumi 2.0 was a significant improvement over Toktumi 1.0, but it still struggled with answer accuracy, understanding certain concepts, and generating repetitive text loops. (For details, see <a href="https://sixfingers.hashnode.dev/toktumi-2-en"><strong>Part 2</strong></a>).</p>
<p>Earlier experiments had shown that <strong>training on Question–Answer (QA) and Instruction–Answer (IA) pairs</strong> produced good results. I therefore decided to develop this approach further, using previously collected texts and the Google Gemini API (<strong>Gemini 3.1 Flash-Lite</strong>) to generate synthetic training data.</p>
<p><strong>I wanted to find out whether continuing to train the model exclusively on short, structured questions, instructions, and dialogues would produce better results than training based mainly on raw texts.</strong></p>
<p>As with the previous stages, <strong>the project is non-commercial, educational, and research-oriented.</strong></p>
<h3>2. A New Approach to Training Data</h3>
<p>In previous versions of Toktumi, a large part of the corpus consisted of raw texts collected from the internet: articles, books, online comments, and other materials. For Toktumi 3.0, I decided to change this approach completely.</p>
<p><strong>Instead of training the model directly on raw texts, I prepared the entire training corpus as short, structured records</strong>, generated from source materials.</p>
<p>I adopted three main types of records:</p>
<ul>
<li><strong>Question → Answer (QA)</strong> — developing knowledge and understanding of concepts.</li>
<li><strong>Instruction → Answer (IA)</strong> — teaching the model to follow instructions.</li>
<li><strong>Statement → Response (dialogue)</strong> — developing conversational skills.</li>
</ul>
<p>Examples of these record categories:</p>
<p><strong>Question and answer:</strong></p>
<blockquote>
<p><em>Jaka jest stolica Francji?</em> [EN: <em>What is the capital of France?</em>]</p>
<p><em>Stolicą Francji jest Paryż.</em> [EN: <em>The capital of France is Paris.</em>]</p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p><strong>Instruction and answer:</strong></p>
<blockquote>
<p><em>Wymień trzy planety Układu Słonecznego.</em> [EN: <em>Name three planets in the Solar System.</em>]</p>
<p><em>Merkury, Wenus i Ziemia.</em> [EN: <em>Mercury, Venus, and Earth.</em>]</p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p><strong>Dialogue:</strong></p>
<blockquote>
<p><em>Chyba zapomniałem zabrać parasola.</em> [EN: <em>I think I forgot to bring an umbrella.</em>]</p>
<p><em>Może sprawdź prognozę pogody, zanim wyjdziesz.</em> [EN: <em>Maybe check the weather forecast before you go out.</em>]</p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p>An important principle was that <strong>each record had to be self-contained</strong>. A question or instruction had to make sense without knowing the source article or any of the other records.</p>
<h3>3. Selecting and Collecting Source Materials</h3>
<p>To prepare the Toktumi 3.0 corpus, I decided to use four main sources of texts I had already collected (see <a href="https://sixfingers.hashnode.dev/toktumi-1-en"><strong>Part 1</strong></a> and <a href="https://sixfingers.hashnode.dev/toktumi-2-en"><strong>Part 2</strong></a>, on Toktumi 1.0 and 2.0): <strong>the Great Dictionary of Polish (WSJP), Wikipedia, Fakt and film and TV series subtitles</strong>.</p>
<p>Each source was intended to develop different abilities in the model:</p>
<ul>
<li><strong>WSJP — language and meaning.</strong> A source containing word definitions, idiomatic expressions, common word combinations, and usage examples. These materials were intended to improve the model's understanding of Polish vocabulary and its ability to define concepts.</li>
<li><strong>Wikipedia — knowledge.</strong> Encyclopedic articles providing information about the world and intended to develop the ability to answer factual questions.</li>
<li><a href="https://www.fakt.pl/"><strong>Fakt.pl</strong></a> – <strong>events.</strong> News articles providing knowledge about specific events, people, and cause-and-effect relationships.</li>
<li><strong>Subtitles — conversation.</strong> Dialogues prepared from film and TV subtitles were intended to teach the model to engage in natural conversation and respond to a speaker's emotions.</li>
</ul>
<p>Generated samples by source:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d7a18e09-116d-44f4-939a-9bf482047aaa.png" alt="034_nowe_dane" style="display:block;margin:0 auto" />

<h3>4. Generating Training Data</h3>
<p>I primarily used the <strong>Google Gemini API</strong> to create training records automatically. It's been about a year since I worked on Toktumi 2.0 (2025). During that time, newer and more advanced models had become available. I replaced the earlier <a href="https://aistudio.google.com/docs/models/gemini-2.5-flash"><strong>Gemini 2.5 Flash</strong></a> (now deprecated) with <a href="https://aistudio.google.com/docs/models/gemini-3.1-flash-lite"><strong>Gemini 3.1 Flash-Lite</strong></a>.</p>
<p>I also used the locally run <a href="https://huggingface.co/unsloth/gemma-4-31B-it-GGUF"><strong>Gemma 4 31B</strong></a> (GGUF version), replacing the earlier <a href="https://huggingface.co/mistralai/Mistral-Nemo-Instruct-2407"><strong>Mistral NeMo 12B</strong></a>. In my tests, Gemma handled Polish better than <a href="https://huggingface.co/Qwen/Qwen3.6-35B-A3B"><strong>Qwen3.6-35B-A3B</strong></a>, another very popular model in 2026.</p>
<p><strong>How it works:</strong> A Python script sends successive source texts through the API to <strong>Gemini 3.1 Flash-Lite</strong>, along with an appropriate prompt defining the generation rules. Based on each source text, the model creates <strong>20 or 30 training records</strong> in the form of Question–Answer and Instruction–Answer pairs. The completed records are automatically saved to .txt files.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/5cd018c5-e68b-4049-b48b-f295e7eab5de.png" alt="035_Generacja" style="display:block;margin:0 auto" />

<p>For the first time in the Toktumi project, I used <strong>film and TV subtitles as a source of training data</strong>. I had already collected a large set of subtitles from online sources, but initial experiments showed that raw dialogue texts were unsuitable for training — the model generated chaotic sequences of utterances without recognizing the speakers or knowing when to end a response.</p>
<p>The solution was to <strong>convert the subtitles into short, self-contained dialogue pairs</strong>, in which one utterance is a logical response to the other. In this form, the subtitles could be used to teach the model to carry on simple conversations.</p>
<p>Converting a subtitle excerpt into a set of training pairs:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/b60b2ab3-596b-4ea6-b4d1-c0c9bbd95eb8.png" alt="036_subtitles" style="display:block;margin:0 auto" />

<p>The code and prompt used to generate the data worked in much the same way as in Toktumi 2.0 (see <a href="https://sixfingers.hashnode.dev/toktumi-2-en">Part 2</a>).</p>
<p>Records generated from <strong>all four sources</strong> had to be checked to ensure they were self-contained. <strong>Every question or instruction had to be understandable on its own, without knowledge of the source article or any previous context.</strong></p>
<p>The model automatically evaluated the records and labeled them <strong>1 – keep</strong> or <strong>0 – discard</strong>. This allowed me to eliminate questions referring to unidentified people, events, or earlier statements.</p>
<p>Below are examples of <strong>rejected, non-self-contained records generated from news articles.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/79bc17d9-350f-43c8-b862-bf99662bb802.png" alt="037_odrzucone" style="display:block;margin:0 auto" />

<h3>DATA PROCESSING SPEED</h3>
<p>Gemini processed <strong>approximately 7–8 source texts per minute through the API</strong> (around 10,000 per day), while the locally run <strong>Gemma 4 in GGUF format processed approximately 1–2 texts per minute</strong> (around 2,000 per day). These are estimated figures.</p>
<p>Generating and cleaning all <strong>6 190 420 training pairs</strong> took <strong>approximately 2.5 months</strong>.</p>
<h3>5. Preparing the Final Toktumi 3.0 Corpus</h3>
<p>As with the previous versions of Toktumi, I combined the prepared data into <strong>one large training corpus</strong>. Before doing so, <strong>I shuffled all 6,190,420 records</strong> so that questions, instructions, and dialogues would be mixed throughout training. I then split the dataset into <strong>99% training data (TRAIN) and 1% validation data (VALID)</strong>, separating the records with the <code>&lt;|endoftext|&gt;</code> token, which the model recognizes as an end-of-text marker.</p>
<p>One small note: despite removing duplicates, the random split means that questions covering similar topics may still have ended up in both the TRAIN and VALID sets.</p>
<p>The synthetically generated texts turned out to be <strong>much shorter than those used in previous versions of Toktumi</strong>, as shown in the illustration below. Each record contained around <strong>43 tokens on average</strong>, while the longest had <strong>174 tokens</strong>. I set the training sequence length (block size) to <strong>256 tokens</strong> to match the shorter records. This parameter also has a significant impact on VRAM usage, so in my case it could not be set too high.</p>
<p>Structure of the training dataset with shuffled records:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a9d8535e-ba0e-4890-bc6b-e27fccab069c.png" alt="038_zbior_uczacy" style="display:block;margin:0 auto" />

<p>Comparison of training dataset sizes: Toktumi 1.0 | Toktumi 2.0 | Toktumi 3.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/1b747d78-b5c5-4f2c-8689-242edb9246e4.png" alt="048_zbiory_treningowe_T1-T2-T3" style="display:block;margin:0 auto" />

<p>Comparing the three versions shows just how much the training dataset had changed. <strong>Toktumi 3.0 contains more than 2.5 times as many records as Toktumi 2.0</strong>, yet the number of words and tokens is approximately <strong>53% lower than in Toktumi 2.0</strong>. The training file itself is <strong>almost half the size</strong>.</p>
<p>In other words, the model trained on <strong>more examples, but far fewer tokens</strong>. This time, I focused primarily on <strong>the quality, brevity, and structure of the training data</strong>, rather than increasing its volume.</p>
<p>The chart below illustrates the lengths of the texts in the training datasets:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/ac7498e7-fdc3-46f2-a653-44fe10b0f71c.png" alt="049_dlogosc_boxploty" style="display:block;margin:0 auto" />

<p>The box plot shows how much shorter the training records became with each new version of Toktumi.</p>
<p><strong>Median length: Toktumi 1.0 – 207 tokens, Toktumi 2.0 – 145 tokens, Toktumi 3.0 – just 41 tokens.</strong></p>
<p>The chart also shows two particularly extreme outliers in Toktumi 1.0 and 2.0 (the individual dots on the right) – very long texts that were not caught by the length filters I used in the earlier stages. Such outliers can inflate the average, so I chose the median as the measure of text length.</p>
<p>In Toktumi 3.0, the records were generated from scratch, giving the texts more standardized lengths.</p>
<h3>6. Training Toktumi 3.0</h3>
<p>Before the main training run, I carried out an experiment on a smaller dataset to see whether it would be better to train a new model from scratch or continue training Toktumi 2.0. <strong>The tests showed that fine-tuning worked better</strong>, so I decided to build on the model's existing language skills.</p>
<p>I kept <strong>the GPT-2 Medium architecture and the Toktumi 2.0 tokenizer</strong> to ensure compatibility between the two versions.</p>
<p>I planned the training run for <strong>a maximum of 15 epochs</strong>, with automatic validation, checkpoint saving, and an <strong>early stopping</strong> mechanism that would end training once the model stopped improving.</p>
<p>The key part of the code used to train the model:</p>
<pre><code class="language-python"># Wczytanie Toktumi 2 i tokenizera
tokenizer = GPT2TokenizerFast.from_pretrained(TOKENIZER_PATH)
model = GPT2LMHeadModel.from_pretrained(MODEL_PATH)

# Przygotowanie danych
train_dataset = TokenBlockDataset(TRAIN_PATH, tokenizer, 256) # block_size = 256
valid_dataset = TokenBlockDataset(VALID_PATH, tokenizer, 256)

# Parametry treningu
training_args = TrainingArguments(
    output_dir=str(CHECKPOINT_DIR),
    num_train_epochs=15,
    per_device_train_batch_size=6,
    gradient_accumulation_steps=4,
    learning_rate=5e-5,
    fp16=True,
    eval_strategy="steps",
    eval_steps=8000,
    save_strategy="steps",
    save_steps=8000,
    save_total_limit=100,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
)

# Fine-tuning z mechanizmem early stopping
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset,
    data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
    callbacks=[EarlyStoppingCallback(early_stopping_patience=6)],
)

trainer.train()
trainer.save_model(str(BEST_MODEL_DIR))
</code></pre>
<p>I decided to <strong>keep all the checkpoints</strong>, which later helped me compare the quality of the model's responses at different stages of training.</p>
<p>Drawing on my previous experience, this time I decided to use an <strong>early stopping</strong> callback instead of manually assessing progress based on perplexity. As it turned out, this was a good decision – I no longer had to decide for myself when to stop training. I set aside a sufficiently large validation set from the dataset, and early stopping automatically halted training when <code>eval_loss</code> stopped improving. <strong>Each validation run took around 3 minutes</strong>, so it did not add much to the overall training time.</p>
<h3>TRAINING BY THE NUMBERS</h3>
<p>Given the high prices of graphics cards (2026), I decided to stick with the NVIDIA GeForce RTX 3060 I had been using all along. I adjusted the training parameters to fit its available memory (12 GB VRAM).</p>
<ul>
<li><strong>One epoch:</strong> consisted of <strong>44,538 steps</strong> and took <strong>around 16 hours</strong> to complete.</li>
<li><strong>Total training time:</strong> approximately <strong>9.5 days</strong>, including breaks and resuming from checkpoints.</li>
<li><strong>Early stopping:</strong> automatically ended training at <strong>step 448,000, after 10.06 epochs</strong>.</li>
<li><strong>Best checkpoint</strong> according to eval_loss: <strong>checkpoint-400000</strong>, reached near the end of the 9th epoch (8.98).</li>
<li><strong>Saved checkpoints:</strong> <strong>56</strong> in total (I used half of them in the subsequent tests).</li>
</ul>
<p>The chart below illustrates the training progress:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/de1681ba-b17e-4598-a99b-6538ec40774e.png" alt="040_uczenie_wykres" style="display:block;margin:0 auto" />

<p>After the ninth epoch, <strong>train_loss continued to decrease, but eval_loss stopped improving</strong>, eventually triggering <strong>early stopping</strong>.</p>
<p>The characteristic stair-step pattern in the train_loss curve (the blue line) appears regularly at the beginning of successive epochs. They do not necessarily indicate a training error and may be related to reshuffling the training data and the way the optimization process works.</p>
<h3>7. Evaluating Model Quality – PROMPTING and Checkpoint Testing</h3>
<p>Next, I tested <strong>29 selected checkpoints</strong> on <strong>two additional test sets of 300 prompts each</strong>, generating a total of <strong>17,400 responses</strong> – a substantial amount of material to analyze.</p>
<p>With the help of AI, I carried out an approximate <strong>FCT (Factuality)</strong> evaluation, taking into account factual accuracy, whether the answers made sense, and how well they matched the prompts. Each correct or reasonable answer received 1 point. This is a simplified evaluation criterion (described in detail in <a href="https://sixfingers.hashnode.dev/toktumi-2-en">Part 2</a>), which allowed me to compare response quality across checkpoints.</p>
<p><strong>Best results (600 prompts):</strong></p>
<ul>
<li><strong>checkpoint-400000:</strong> 431/600 (71.8%)</li>
<li><strong>checkpoint-448000:</strong> 431/600 (71.8%)</li>
<li><strong>checkpoint-432000:</strong> 425/600 (70.8%)</li>
</ul>
<p><strong>I selected checkpoint-400000 for further testing</strong>. It also achieved the lowest <code>eval_loss</code> during training and was automatically identified as the best checkpoint by the early stopping mechanism. It is worth noting that the differences between the top checkpoints were small.</p>
<p>Checkpoint results according to the Factuality (FCT) criterion – whether responses were correct, factually accurate, and made sense:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/63751c30-3212-4500-b8bd-05fe3bbf6979.png" alt="050_find_best_chckp" style="display:block;margin:0 auto" />

<p><strong>An interesting observation:</strong> When asked who the first President of the United States was (using greedy decoding), checkpoint-384000 correctly answered George Washington, checkpoint-400000 incorrectly answered George W. Bush, and checkpoint-448000 gave the correct answer again. This shows that <strong>longer training does not always guarantee better responses</strong>. The model can produce the correct answer, but it does not always select it as the most probable one.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/19bee6a6-d90d-4c03-8009-d2e450eb756d.png" alt="041_prezydent" style="display:block;margin:0 auto" />

<p>The full prompt sets and model responses, organized by checkpoint, are available here:</p>
<p><a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_3.0_29_checkpoints_300_prompts_set_1.txt"><strong>300 Pack 1</strong></a> and the second set, <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_3.0_29_checkpoints_300_prompts_set_2.txt"><strong>300 Pack 2</strong></a>.</p>
<h3>8. Comparing All Three Model Versions: Toktumi 1-2-3</h3>
<p>Here, <strong>I examine how Toktumi 3.0 compares with the previous versions (described in</strong> <a href="https://sixfingers.hashnode.dev/toktumi-1-en"><strong>Part 1</strong></a> <strong>and</strong> <a href="https://sixfingers.hashnode.dev/toktumi-2-en"><strong>Part 2</strong></a><strong>)</strong>. I return to the set of <strong>150 prompts</strong> used earlier to evaluate Toktumi 1.0 and 2.0.</p>
<p>The test shows <strong>whether Toktumi 3.0 correctly recognizes the prompt or instruction (IF)</strong> and then <strong>whether its response is factually correct (where factual accuracy can be assessed) or makes sense (a more subjective assessment in the case of dialogues) (FCT)</strong>. I classified the results as <strong>YES/YES, YES/HAL, or NO</strong>, where <strong>HAL</strong> indicates a hallucination despite correctly recognizing the structure of the prompt (for details, see <a href="https://sixfingers.hashnode.dev/toktumi-2-en">Part 2</a> on Toktumi 2.0).</p>
<h3>Toktumi 1.0 Results</h3>
<table>
<thead>
<tr>
<th>Classification [IF / FCT]</th>
<th align="right">Number of cases</th>
</tr>
</thead>
<tbody><tr>
<td><strong>YES / YES</strong> – correct structure and correct answer</td>
<td align="right">85</td>
</tr>
<tr>
<td><strong>YES / HAL</strong> – correct structure, but a hallucination</td>
<td align="right">33</td>
</tr>
<tr>
<td><strong>NO</strong> – failure to recognize the instruction correctly</td>
<td align="right">32</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Correct answers (YES / YES)</strong></td>
<td align="right"><strong>56.7%</strong></td>
</tr>
</tbody></table>
<h3>Toktumi 2.0 Results</h3>
<table>
<thead>
<tr>
<th>Classification [IF / FCT]</th>
<th align="right">Number of cases</th>
</tr>
</thead>
<tbody><tr>
<td><strong>YES / YES</strong> – correct structure and correct answer</td>
<td align="right">102</td>
</tr>
<tr>
<td><strong>YES / HAL</strong> – correct structure, but a hallucination</td>
<td align="right">42</td>
</tr>
<tr>
<td><strong>NO</strong> – failure to recognize the instruction correctly</td>
<td align="right">6</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Correct answers (YES / YES)</strong></td>
<td align="right"><strong>68.0%</strong></td>
</tr>
</tbody></table>
<h3>Toktumi 3.0 Results</h3>
<table>
<thead>
<tr>
<th>Classification [IF / FCT]</th>
<th align="right">Number of cases</th>
</tr>
</thead>
<tbody><tr>
<td><strong>YES / YES</strong> – correct structure and correct answer</td>
<td align="right">114</td>
</tr>
<tr>
<td><strong>YES / HAL</strong> – correct structure, but a hallucination</td>
<td align="right">31</td>
</tr>
<tr>
<td><strong>NO</strong> – failure to recognize the instruction correctly</td>
<td align="right">5</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Correct answers (YES / YES)</strong></td>
<td align="right"><strong>76.0%</strong></td>
</tr>
</tbody></table>
<p><strong>The results show a clear improvement in model quality.</strong> The percentage of correct answers increased from <strong>56.7% for Toktumi 1.0 to 76% for Toktumi 3.0</strong>, while the number of prompts the model failed to recognize dropped from <strong>32 to 5 out of 150</strong>.</p>
<p>Toktumi 3.0 follows instructions much better and provides correct answers more often, although it still makes factual errors and generates hallucinations.</p>
<p><strong>Results and responses for Toktumi 1-2-3 (150 prompts)</strong> <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_1-2-3_150_prompts_comparison.txt"><strong>VIEW HERE</strong></a></p>
<hr />
<h3>9. Qualitative Tests: What Can Toktumi 3.0 Do?</h3>
<p>Here, I take a closer look at <strong>the capabilities and limitations of Toktumi 3.0</strong>.</p>
<p>I used the two previously prepared sets of 300 prompts (<strong>300 Pack 1 and 300 Pack 2</strong>). I generated all <strong>600 responses</strong> using <strong>greedy decoding</strong>.</p>
<p>The results and conclusions are presented below.</p>
<hr />
<h4>a) RESPONSE STYLE: SHORTER ANSWERS</h4>
<p>The first noticeable change is how the model ends its responses. Unlike earlier versions, Toktumi 3.0 usually gives <strong>a short answer</strong> without unnecessarily continuing the text. The repetitive loops characteristic of Toktumi 1 and 2 have also been greatly reduced.</p>
<p>An example of <strong>text repetition</strong> – a problem significantly reduced in Toktumi 3. The latest version of the model is much less likely to get stuck repeating itself and more likely to provide short, correct, and coherent answers on the given topic.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/84857d03-a8b3-4483-afae-825868aa1005.png" alt="042_zapetlenie" style="display:block;margin:0 auto" />

<p><strong>Short answers</strong> have become a hallmark of Toktumi 3.0:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, no. 7):</strong> <em>W jakiej dyscyplinie sportu specjalizowała się Justyna Kowalczyk?</em> [EN: <em>Which sport did Justyna Kowalczyk specialize in?</em>]</p>
<p><strong>Answer:</strong> <em>Biegi narciarskie.</em> [EN: <em>Cross-country skiing.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 90):</strong> <em>Jak nazywa się jednostka dziedziczenia znajdująca się w DNA?</em> [EN: <em>What is the unit of heredity found in DNA called?</em>]</p>
<p><strong>Answer:</strong> <em>Gen.</em> [EN: <em>A gene.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 43):</strong> <em>Jak nazywa się naturalny satelita naszej planety?</em> [EN: <em>What is the name of our planet's natural satellite?</em>]</p>
<p><strong>Answer:</strong> <em>Księżyc.</em> [EN: <em>The Moon.</em>]</p>
</blockquote>
<hr />
<h4>b) WHAT DOES THE MODEL DO WELL?</h4>
<p><strong>OK 1. Simple questions and following instructions</strong></p>
<p>The model handles instructions that require giving specific examples, creating a short list, or providing a straightforward answer quite well.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, no. 4):</strong> <em>Wymień trzy rzeczy, które przydają się podczas pikniku.</em> [EN: <em>Name three things that are useful for a picnic.</em>]</p>
<p><strong>Answer:</strong> <em>Podczas pikniku przydają się: koc, napoje oraz przekąski.</em> [EN: <em>Useful things for a picnic include a blanket, drinks, and snacks.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 100):</strong> <em>Wymień trzy polskie owoce sezonowe.</em> [EN: <em>Name three seasonal fruits grown in Poland.</em>]</p>
<p><strong>Answer:</strong> <em>Truskawki, maliny, jagody.</em> [EN: <em>Strawberries, raspberries, and blueberries.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 142):</strong> <em>Wymień dwa sposoby podróżowania po mieście bez własnego samochodu.</em> [EN: <em>Name two ways to get around a city without your own car.</em>]</p>
<p><strong>Answer:</strong> <em>Dwa sposoby to korzystanie z komunikacji miejskiej, takiej jak autobusy lub tramwaje, oraz chodzenie pieszo.</em> [EN: <em>Two ways are using public transport, such as buses or trams, and walking.</em>]</p>
</blockquote>
<hr />
<p><strong>OK 2. Definitions and explanations of basic concepts</strong></p>
<p>Toktumi 3 can also <strong>produce accurate, detailed explanations</strong>:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, no. 34):</strong> <em>Wyjaśnij prostymi słowami, do czego służy termostat.</em> [EN: <em>Explain in simple terms what a thermostat is used for.</em>]</p>
<p><strong>Answer:</strong> <em>Termostat to urządzenie, które włącza lub wyłącza grzanie, gdy temperatura w pomieszczeniu spadnie poniżej ustawionego poziomu, co pozwala utrzymać w nim komfortową temperaturę.</em> [EN: <em>A thermostat is a device that switches the heating on or off when the room temperature drops below a set level, helping maintain a comfortable temperature.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 106):</strong> <em>Opisz, czym jest szczepionka i jaki jest cel jej stosowania.</em> [EN: <em>Describe what a vaccine is and what it is used for.</em>]</p>
<p><strong>Answer:</strong> <em>Szczepionka to preparat biologiczny, który uczy układ odpornościowy rozpoznawać i zwalczać konkretne drobnoustroje, takie jak wirusy czy bakterie, chroniąc przed zachorowaniem lub łagodząc przebieg choroby.</em> [EN: <em>A vaccine is a biological preparation that teaches the immune system to recognize and fight specific microorganisms, such as viruses or bacteria, protecting against illness or reducing its severity.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 125):</strong> <em>Opisz, na czym polega funkcja Rzecznika Praw Obywatelskich w Polsce.</em> [EN: <em>Describe the role of the Commissioner for Human Rights in Poland.</em>]</p>
<p><strong>Answer:</strong> <em>Rzecznik Praw Obywatelskich stoi na straży wolności i praw człowieka oraz obywatela, badając, czy organy władzy publicznej działają zgodnie z prawem i czy nie przekraczają swoich uprawnień.</em> [EN: <em>The Commissioner for Human Rights safeguards human and civil rights and freedoms by examining whether public authorities act in accordance with the law and do not exceed their powers.</em>]</p>
</blockquote>
<p>In these examples, the model produces <strong>coherent responses</strong>, using appropriate vocabulary and maintaining a logical structure.</p>
<p>It can finally give a correct <strong>definition of a pencil</strong>, something earlier versions of Toktumi sometimes struggled with.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/ffb3de45-a084-4df2-9a8d-562e8697f4d2.png" alt="043_olowek" style="display:block;margin:0 auto" />

<hr />
<p><strong>OK 3. Understanding words and idioms</strong></p>
<p>The responses concerning <strong>the meanings of Polish words and idiomatic expressions</strong>, which the model learned from a Polish dictionary, are also interesting.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, no. 22):</strong> <em>Wyjaśnij słowo „zawzięty” w odniesieniu do osoby dążącej do celu.</em> [EN: <em>Explain what the Polish word "zawzięty" (tenacious) means when describing someone pursuing a goal.</em>]</p>
<p><strong>Answer:</strong> <em>Oznacza to, że osoba ta jest bardzo zdeterminowana, nie poddaje się łatwo i z wielkim zapałem realizuje swoje zamierzenia, nie zważając na przeszkody.</em> [EN: <em>It means that the person is very determined, does not give up easily, and pursues their goals with great enthusiasm, regardless of obstacles.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 292):</strong> <em>Co znaczy frazeologizm „grać na zwłokę”, gdy ktoś unika podjęcia decyzji?</em> [EN: <em>What does the Polish expression "grać na zwłokę" ("play for time") mean when someone avoids making a decision?</em>]</p>
<p><strong>Answer:</strong> <em>Oznacza to celowe opóźnianie jakiegoś działania lub decyzji, aby zyskać na czasie i uniknąć podjęcia właściwego wyboru.</em> [EN: <em>It means deliberately delaying an action or decision to gain time and avoid making the actual choice.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 218):</strong> <em>Jak rozumiesz „rzucać kłody pod nogi”?</em> [EN: <em>What do you understand by the Polish expression "rzucać kłody pod nogi" ("put obstacles in someone's way")?</em>]</p>
<p><strong>Answer:</strong> <em>To określenie sytuacji, w której ktoś celowo utrudnia innym osiągnięcie zamierzonego celu.</em> [EN: <em>It describes a situation in which someone deliberately makes it harder for others to achieve their intended goal.</em>]</p>
</blockquote>
<p>Importantly, the model can not only explain a word or expression but also <strong>put its meaning into its own words</strong>, rather than simply repeating the dictionary definition.</p>
<hr />
<p><strong>OK 4. Short, natural dialogues</strong></p>
<p>In Toktumi 3.0, I used <strong>movie and TV subtitles</strong> for the first time, converting them into self-contained dialogue pairs. The examples below show that the model can now tailor its responses to the situation and stop after a single reply – as in a real conversation.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, no. 230):</strong> <em>Przepraszam za spóźnienie, autobus utknął w korku.</em> [EN: <em>Sorry I'm late, the bus got stuck in traffic.</em>]</p>
<p><strong>Answer:</strong> <em>Nie szkodzi, najważniejsze, że już jesteś i możemy zaczynać.</em> [EN: <em>No problem. The important thing is that you're here now, and we can get started.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 172):</strong> <em>Obiecaj mi, że zachowasz to dla siebie.</em> [EN: <em>Promise me you'll keep this to yourself.</em>]</p>
<p><strong>Answer:</strong> <em>Możesz być spokojny, nikomu nie pisnę ani słowa.</em> [EN: <em>Don't worry, I won't breathe a word to anyone.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 238):</strong> <em>Wreszcie dostałem tę pracę, o której ci opowiadałem!</em> [EN: <em>I finally got that job I told you about!</em>]</p>
<p><strong>Answer:</strong> <em>To fantastyczna wiadomość, naprawdę się cieszę, że udało ci się ją dostać.</em> [EN: <em>That's fantastic news! I'm really happy you got it.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 219):</strong> <em>Nie uwierzysz, co mi się dzisiaj przydarzyło!</em> [EN: <em>You won't believe what happened to me today!</em>]</p>
<p><strong>Answer:</strong> <em>Co takiego?</em> [EN: <em>What happened?</em>]</p>
</blockquote>
<p>Some of the reactions are surprisingly natural. The model can recognize the emotional tone of a conversation, show interest, or respond in everyday, colloquial language.</p>
<hr />
<h4>c) WHAT DOES THE MODEL STILL STRUGGLE WITH?</h4>
<p><strong>Problem 1. Factual knowledge – the right format, but the wrong information</strong></p>
<p>The biggest challenge remains specific facts: names, dates, historical events, and concrete information about the world.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, no. 54):</strong> <em>Kto był pierwszym prezydentem Stanów Zjednoczonych?</em> [EN: <em>Who was the first President of the United States?</em>]</p>
<p><strong>Answer:</strong> <em>George W. Bush.</em> [EN: <em>George W. Bush.</em>]</p>
<p>(Should be: <em>George Washington.</em>)</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, no. 16):</strong> <em>Podaj imię i nazwisko pierwszej kobiety, która poleciała w kosmos.</em> [EN: <em>Give the first and last name of the first woman to travel into space.</em>]</p>
<p><strong>Answer:</strong> <em>Jurij Gagarin.</em> [EN: <em>Yuri Gagarin.</em>]</p>
<p>(Should be: <em>Valentina Tereshkova.</em>)</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 1):</strong> <em>Podaj nazwę norweskiej stolicy.</em> [EN: <em>Name the capital of Norway.</em>]</p>
<p><strong>Answer:</strong> <em>Stolicą Norwegii jest Bermudy.</em> [EN: <em>The capital of Norway is Bermuda.</em>]</p>
<p>(Should be: <em>Oslo.</em>)</p>
</blockquote>
<p>These examples clearly show that <strong>the model has learned how to answer questions much better than it has learned factual knowledge</strong>. It does not refuse to answer or express uncertainty – it simply <strong>provides false information (hallucinates).</strong></p>
<p>The responses above were generated using <strong>greedy decoding</strong>. After introducing sampling with <strong>top_k=2</strong>, the model was also able to give the correct answers: <strong>George Washington in 3 out of 10 attempts</strong> and <strong>Oslo in 6 out of 20 attempts</strong>.</p>
<p>After a few attempts, the model eventually gets the capital of Norway right.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d4c3d4a0-921d-4cd3-94d2-703bf57305b2.png" alt="044_Oslo" style="display:block;margin:0 auto" />

<p>However, sampling does not always help. When asked about <strong>the first woman in space</strong>, the model failed to name <strong>Valentina Tereshkova</strong> even once in <strong>100 attempts</strong>. Instead, its answers included Yuri Gagarin, Marie Skłodowska-Curie, fashion designer Jil Sander, actress Gillian Anderson, and even Polish writer and satirist Maria Czubaszek.</p>
<p>I searched the training dataset and found that this information actually appeared in it several times.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/19204fb5-a903-45cd-a1b5-91d1c622a4b0.png" alt="045_Tierieszkowa" style="display:block;margin:0 auto" />

<p>So I decided to <strong>rephrase the question</strong> as <em>„Kim była Walentina Tierieszkowa?”</em> [EN: <em>Who was Valentina Tereshkova?</em>]. This time, the model answered correctly:</p>
<blockquote>
<p><em>„Była radziecką kosmonautką, która jako pierwsza kobieta w historii poleciała w kosmos."</em> [EN: <em>She was a Soviet cosmonaut who became the first woman in history to travel into space.</em>]</p>
</blockquote>
<p>As we can see, the model <strong>knows this information but struggles to associate it with the right person</strong>.</p>
<hr />
<p><strong>Problem 2. The model can 'ruin' a correct answer by adding an incorrect explanation</strong></p>
<p>This is a particularly interesting type of hallucination.</p>
<blockquote>
<p><strong>Prompt (v3, no. 1):</strong> <em>Wskaż, nad jaką rzeką położony jest Sandomierz.</em> [EN: <em>Name the river on which Sandomierz is located.</em>]</p>
<p><strong>Answer:</strong> <em>Sandomierz leży nad rzeką Wisłą, która jest dopływem Wisły.</em> [EN: <em>Sandomierz is located on the Vistula River, which is a tributary of the Vistula.</em>]</p>
</blockquote>
<p>The first part of the answer is correct. The second makes no sense.</p>
<p>Another example – a question about photosynthesis:</p>
<blockquote>
<p><strong>Prompt (v3, no. 43):</strong> <em>Jaki gaz rośliny pobierają z atmosfery w procesie fotosyntezy?</em> [EN: <em>Which gas do plants absorb from the atmosphere during photosynthesis?</em>]</p>
<p><strong>Answer:</strong> <em>W procesie fotosyntezy z atmosfery pobierają dwutlenek węgla, który jest niezbędny do oddychania dla większości roślin.</em> [EN: <em>During photosynthesis, plants absorb carbon dioxide from the atmosphere, which is essential for respiration in most plants.</em>]</p>
</blockquote>
<p>The model correctly identifies carbon dioxide but incorrectly explains its role.</p>
<hr />
<p><strong>Problem 3. Understanding meaning does not always go hand in hand with knowing the language</strong></p>
<p>Despite producing many good definitions, the model still struggles with certain expressions, even common ones.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, no. 134):</strong> <em>Co oznacza „mieć dwie lewe ręce”?</em> [EN: <em>What does the Polish expression “mieć dwie lewe ręce” (literally, “to have two left hands”) mean?</em>]</p>
<p><strong>Answer:</strong> <em>Oznacza to, że osoba wykonująca pracę ma dwie ręce jednocześnie, co często zdarza się w zawodach wymagających precyzji lub w sytuacjach wymagających dużej siły fizycznej.</em> [EN: <em>It means that a person doing a job has two hands at the same time, which often happens in professions requiring precision or in situations demanding considerable physical strength.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, no. 296):</strong> <em>Wyjaśnij pojęcie „syzyfowa praca”.</em> [EN: <em>Explain the meaning of “syzyfowa praca” (Sisyphean labor).</em>]</p>
<p><strong>Answer:</strong> <em>Jest to określenie sytuacji, w której ktoś wykonuje swoje zadania w sposób bardzo powolny, bez wkładania w nie żadnego wysiłku, co prowadzi do szybkiego zakończenia pracy.</em> [EN: <em>It describes a situation in which someone carries out their tasks very slowly, without putting in any effort, which leads to the work being completed quickly.</em>]</p>
</blockquote>
<p>In the first case, the model interprets the idiom almost literally. In the second, it produces a fluent explanation that is nevertheless a hallucination and does not reflect the expression's actual dictionary meaning.</p>
<p>After checking the training dataset, I found that <strong>“mieć dwie lewe ręce”</strong> appeared <strong>40 times</strong>, while <strong>“syzyfowa praca”</strong> appeared <strong>58 times</strong> – including both definitions and uses in dialogues in different contexts. Here, too, simply rephrasing the prompt was enough to produce correct answers straight away:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co potocznie oznacza zwrot mieć dwie lewe ręce?</em> [EN: <em>What does the expression “mieć dwie lewe ręce” (to be all thumbs) mean in everyday language?</em>]</p>
<p><strong>Answer:</strong> <em>Oznacza to, że ktoś jest niezdarny, mało sprawny fizycznie lub nie radzi sobie z wykonywaniem prostych czynności manualnych.</em> [EN: <em>It means that someone is clumsy, lacks physical dexterity, or struggles with simple manual tasks.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest syzyfowa praca?</em> [EN: <em>What is Sisyphean labor?</em>]</p>
<p><strong>Answer:</strong> <em>Syzyfowa praca to określenie na bardzo ciężką, żmudną i wymagającą wysiłku pracę, która nie przynosi żadnych widocznych efektów.</em> [EN: <em>Sisyphean labor refers to very hard, tedious, and demanding work that produces no visible results.</em>]</p>
</blockquote>
<p>The conclusion is similar to the Tereshkova example: the model has absorbed the knowledge but does not always select the correct answer as the most obvious one. <strong>Sometimes all it takes is rephrasing the prompt or asking the question differently</strong> to get the desired result.</p>
<hr />
<p><strong>Problem 4. Everyday situations and common sense</strong></p>
<blockquote>
<p><strong>Prompt (v4, no. 201):</strong> <em>Co zrobić, gdy na kuchennej podłodze leżą odłamki rozbitej szklanki?</em> [EN: <em>What should you do if there are shards of a broken drinking glass on the kitchen floor?</em>]</p>
<p><strong>Answer:</strong> <em>Należy niezwłocznie wezwać pogotowie ratunkowe, dzwoniąc pod numer alarmowy 112, a następnie zabezpieczyć miejsce zdarzenia i nie próbować samodzielnie naprawiać rozbitych szklanek czy talerzy.</em> [EN: <em>You should immediately call an ambulance by dialing the emergency number 112, then secure the scene and not attempt to repair broken glasses or plates yourself.</em>]</p>
</blockquote>
<p>The model recognizes that safety is involved but suggests an action that is completely disproportionate to the situation.</p>
<p>Another example:</p>
<blockquote>
<p><strong>Prompt (v4, no. 263):</strong> <em>Kupiłem aparat fotograficzny. Od czego zacząć naukę robienia zdjęć?</em> [EN: <em>I bought a camera. Where should I start learning how to take photos?</em>]</p>
<p><strong>Answer:</strong> <em>Najpierw naucz się obsługi miksera, potem spróbuj zrobić kilka zdjęć w różnych warunkach oświetleniowych, a na koniec spróbuj zrobić kilka ujęć w różnych miejscach, aby sprawdzić, jak najlepiej ustawić aparat.</em> [EN: <em>First, learn how to use a mixer, then try taking a few photos in different lighting conditions, and finally take some shots in different locations to see how best to set up your camera.</em>]</p>
</blockquote>
<p>The answer starts with a random recommendation about using a mixer, then returns to the topic of photography, although in a rather chaotic way.</p>
<p>It is worth noting, however, that <strong>Toktumi 3 can also offer sensible advice about everyday situations</strong>. For example:</p>
<blockquote>
<p><strong>Prompt (v4, no. 93):</strong> <em>Dlaczego nie wolno dotykać przewodów wystających z uszkodzonego gniazdka?</em> [EN: <em>Why shouldn't you touch wires sticking out of a damaged electrical outlet?</em>]</p>
<p><strong>Answer:</strong> <em>Dotykanie przewodów elektrycznych może doprowadzić do zwarcia, awarii instalacji lub porażenia prądem, dlatego należy zachować ostrożność.</em> [EN: <em>Touching electrical wires can cause a short circuit, damage to the electrical installation, or an electric shock, so you should be careful.</em>]</p>
</blockquote>
<p>These examples show that <strong>common-sense knowledge is still a challenge for the model</strong>. Toktumi 3.0 can sometimes give sound, practical advice, but it also occasionally suggests actions that are disproportionate to the situation or combines information that does not fit together.</p>
<hr />
<p><strong>Problem 5. Natural conversation does not always mean understanding intent</strong></p>
<p>Although the model handles short conversational responses well, it performs less effectively when the user expects it to take a specific action.</p>
<blockquote>
<p><strong>Prompt (v4, no. 275):</strong> <em>Sąsiad zaprosił nas na grill, ale nie możemy przyjść. Jak grzecznie odmówić?</em> [EN: <em>Our neighbor invited us to a barbecue, but we can't make it. How can we politely decline?</em>]</p>
<p><strong>Answer:</strong> <em>- Jasne, nie ma problemu.</em> [EN: <em>- Sure, no problem.</em>]</p>
</blockquote>
<p>Instead of suggesting a polite refusal, the model generates a short conversational response that does not follow the instruction.</p>
<p>Similarly, in a situation that calls for advice:</p>
<blockquote>
<p><strong>Prompt (v4, no. 83):</strong> <em>Muszę jutro wcześnie wstać. Jak się przygotować, żeby nie zaspać?</em> [EN: <em>I have to get up early tomorrow. How can I prepare so I don't oversleep?</em>]</p>
<p><strong>Answer:</strong> <em>Zacznij od szklanki wody, to zawsze pomaga na żołądek.</em> [EN: <em>Start with a glass of water; that always helps your stomach.</em>]</p>
</blockquote>
<p>The model responds fluently but fails to give the advice the user was probably expecting.</p>
<p>This problem may stem from the way the <strong>short, self-contained dialogue pairs</strong> were prepared, without the broader context of a conversation. The model has learned to respond naturally to individual statements, but it does not always correctly recognize <strong>the intentions and roles of the speakers</strong>.</p>
<p>One possible solution would be <strong>to train the model on longer, multi-turn dialogues</strong> that preserve the context and the roles of the speakers. However, this would require more complex data preparation.</p>
<hr />
<h4>d) WHEN TOKTUMI GETS WEIRD</h4>
<h3>1. Where Do Babies Come From? – The Question That Makes Toktumi Blush</h3>
<p>I decided to see how Toktumi 3.0 would handle a seemingly simple question: <strong>“Skąd się biorą dzieci?”</strong> [EN: <em>“Where do babies come from?”</em>]. I tested <strong>29 checkpoints</strong> (half of those saved) to see how the answers changed as training progressed. The result was surprising – <strong>not a single checkpoint gave the correct answer</strong>. Instead of explaining the biology, the model behaved almost like an embarrassed adult trying to avoid an awkward subject:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Checkpoint-48000:</strong> <em>To nie jest pytanie, na które chciałbyś usłyszeć odpowiedź.</em> [EN: <em>That's not a question you'd want to hear the answer to.</em>]</p>
<p><strong>Checkpoint-80000:</strong> <em>To nie jest temat do rozmowy przy śniadaniu.</em> [EN: <em>That's not something to discuss over breakfast.</em>]</p>
<p><strong>Checkpoint-272000:</strong> <em>To pytanie, na które nie dostaniesz odpowiedzi.</em> [EN: <em>That's a question you won't get an answer to.</em>]</p>
<p><strong>Checkpoint-400000:</strong> <em>Nie mam pojęcia, ale na pewno nie są to dzieci, o których teraz wspominasz.</em> [EN: <em>I have no idea, but they're certainly not the children you're talking about right now.</em>]</p>
</blockquote>
<p>Interestingly, <strong>all I had to do was ask the question differently</strong>. When asked <em>“Jak powstają dzieci?”</em> [EN: <em>“How are babies made?”</em>], the model gave the correct answer:</p>
<blockquote>
<p><em>Powstają one w wyniku zapłodnienia komórki jajowej przez plemnik, co prowadzi do powstania zygoty.</em> [EN: <em>They result from a sperm cell fertilizing an egg, which leads to the formation of a zygote.</em>]</p>
</blockquote>
<p>The answer sounds more like something from biology or medicine than everyday conversation, but it is nevertheless correct.</p>
<p>This is an interesting example of <strong>how a model reproduces the way language reflects our view of the world</strong> – it imitates not only what we know, but also the ways of thinking and associations embedded in language.</p>
<hr />
<h3>2. Does God Exist? – AI's Existential Crisis</h3>
<p>My next experiment involved the question <strong>“Czy Bóg istnieje?”</strong> [EN: <em>“Does God exist?”</em>], which I asked all <strong>29 checkpoints</strong> to see how their answers changed as training progressed. The results were surprising – Toktumi <strong>repeatedly switched from “yes” to “no” and back again</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/767d606a-6a7b-4abb-801e-74d64e6ac333.png" alt="046_czy_bog_istnieje" style="display:block;margin:0 auto" />

<p>As training progressed, a pattern emerged: <strong>“no” was more common at the beginning, “yes” clearly dominated in the middle, and “no” took over again near the end</strong>. So, half-jokingly, you could say that during training Toktumi first <strong>“found faith” and then “lost it”</strong>!</p>
<hr />
<h3>3. When AI Learns to Swear</h3>
<p>The Polish dictionary also contains <strong>vulgar and derogatory words</strong> – a natural part of the language. Alongside strong swear words, there are terms such as “patafian” (blockhead), “łajza” (loser), and “zadupie” (a dump in the middle of nowhere), as well as expressions like “zakuty łeb” (thickhead) and “dupa wołowa” (clumsy fool).</p>
<p>When processing entries like these, Gemini often blocked the generation of responses because of its moderation mechanisms. That is why I used the less restrictive locally run Gemma model to create question–answer and instruction–answer pairs from this material as well.</p>
<p>Let's see whether Toktumi can swear. As it turns out, the model <strong>has picked up a range of vulgar vocabulary</strong>, but sometimes mixes strong swear words with completely harmless sayings:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wymień znane ci przekleństwa.</em> [EN: <em>List some swear words you know.</em>]</p>
<p><strong>Answer:</strong> <em>Do popularnych przekleństw należą: „kurwa mać”, „spadaj” oraz „srali muchy będzie wiosna”.</em> [EN: <em>Popular swear words include “for fuck's sake,” “get lost,” and “when flies shit, spring will come.”</em>]</p>
</blockquote>
<p>At other times, it gets stuck in a loop of repetition:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie są najgorsze przekleństwa w języku polskim?</em> [EN: <em>What are the worst swear words in Polish?</em>]</p>
<p><strong>Answer:</strong> <em>Najgorszymi przekleństwami są wyrazy takie jak kurczę, ojejku, ojejku, ojejku czy też słowa typu kurczę pieczone.</em> [EN: <em>The worst swear words include expressions like “darn,” “oh dear,” “oh dear,” “oh dear,” or phrases such as “good grief.”</em>]</p>
</blockquote>
<p>And sometimes it even refuses to follow the instruction:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wyraź swoją złość, używając mocnego przekleństwa.</em> [EN: <em>Express your anger using a strong swear word.</em>]</p>
<p><strong>Answer:</strong> <em>Nie będę się zniżał do twojego poziomu, bo to nie jest miejsce na takie rozmowy.</em> [EN: <em>I'm not going to stoop to your level, because this isn't the place for that kind of conversation.</em>]</p>
</blockquote>
<h2>SUMMARY AND FINAL CONCLUSIONS</h2>
<p>The Toktumi project showed how much can be achieved by developing a relatively small GPT-2 Medium language model. All three generations were built on the same architecture, but differed in how the training data was prepared and how training was carried out. <strong>The most important changes were not to the model's size, but to what it was taught and how.</strong></p>
<h3>Three Generations – Three Stages of Development</h3>
<ul>
<li><strong>Toktumi 1.0 – the model learned to write in Polish.</strong> It could generate fluent text, but instead of answering questions, it often continued articles or produced comments unrelated to the prompt. Learning the language alone was not enough to hold a meaningful conversation.</li>
<li><strong>Toktumi 2.0 – the model learned to answer questions.</strong> Rebuilding the training dataset and introducing questions and instructions brought the biggest breakthrough in instruction recognition. The number of prompts it failed to recognize dropped from <strong>32 to 6 out of 150</strong>. The model was now much more likely to understand what kind of answer was expected.</li>
<li><strong>Toktumi 3.0 – the model learned to hold simple conversations.</strong> Training on <strong>carefully processed and structured data</strong>, including dialogue pairs, enabled it to respond appropriately to what someone said, taking into account the context and emotional tone. In the comparison test, it achieved <strong>114 correct answers out of 150</strong> – the highest result of all three models.</li>
</ul>
<h3>The Most Interesting Lessons from These Experiments</h3>
<ul>
<li><strong>A model primarily learns from what is in its training data.</strong> If the data contains no examples of telling jokes, programming, or solving problems, the model will not learn those skills. An LLM can combine information, generalize, and create new content, but its capabilities depend primarily on what it has learned from its training data.</li>
<li><strong>A model may know the correct answer but not always choose it as the most likely one.</strong> The checkpoint analysis showed that a model can answer correctly, then incorrectly after further training, and later correctly again. This does not necessarily mean it has lost previously acquired knowledge – training changes the probabilities of the responses it generates.</li>
<li><strong>The quality and structure of training data can matter more than its quantity.</strong> Toktumi 1.0 learned from millions of raw texts, but significant improvements came only after introducing structured questions, instructions, and dialogues. These experiments showed how important it is to select and process source materials properly.</li>
<li><strong>Sometimes the secret lies in how you phrase the prompt.</strong> A model may have learned a particular fact but fail to recall it when asked a specific question. Sometimes simply rephrasing the prompt, adding context, or giving the model a helpful hint is enough to get the correct answer.</li>
<li><strong>A model learns not only facts and words, but also human ways of seeing the world.</strong> Our language contains opinions, beliefs, and stereotypes that a model may learn to reproduce. It can do so so naturally and convincingly that it is easy to get the impression that it is expressing its own genuine views.</li>
</ul>
<h3>Final Conclusion</h3>
<p>Completing this project required bringing together knowledge from many fields – <strong>programming, data processing, linguistics, machine learning, and hardware configuration and optimization</strong>. In practice, this meant dealing with technical challenges as well as questions about how a model learns language, acquires knowledge, and learns to respond.</p>
<p><strong>Despite the hardware limitations and the constraints of the GPT-2 Medium architecture, I managed to achieve the project's main goals.</strong> The experiment showed that even one person, working with home computer hardware, can build and train a functioning language model.</p>
<h2>Toktumi 3.0 on Hugging Face Spaces</h2>
<p>You can try <strong>Toktumi 3.0</strong> on <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-3.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-3.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fdbc2813-8356-49b0-b25b-0cc779858068.png" alt="53_T3_interface" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[How to Build a Large Language Model from Scratch? Toktumi 2.0 – Part 2/3]]></title><description><![CDATA[Toktumi 2.0: retraining Polish GPT-2 from scratch using cleaner data and 616K synthetic Q&A and instruction pairs, followed by model evaluation.]]></description><link>https://sixfingers.hashnode.dev/toktumi-2-en</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-2-en</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[nlp]]></category><category><![CDATA[synthetic data]]></category><category><![CDATA[pytorch]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 21:12:41 GMT</pubDate><content:encoded><![CDATA[<h1><strong>CHAPTER II. Toktumi 2.0. Second Training Run.</strong></h1>
<p>In this part, I describe <strong>the second stage of the Toktumi project</strong>, which is <strong>educational, research-oriented, and non-commercial</strong>. I introduce improvements based on the experience and lessons learned from training <strong>Toktumi 1.0</strong>.</p>
<p><strong>As a reminder:</strong> In the previous stage, <strong>I built and trained a Polish-language GPT-2 Medium model from scratch</strong>, using <strong>3 115 002 text samples that I had collected and cleaned myself</strong>. I prepared my own tokenizer, carried out the training, and tested the quality of the model's responses. As a result, Toktumi 1.0 learned to generate grammatically correct text, but it often struggled to recognize instructions and provide correct answers.</p>
<p>For more details, <a href="https://sixfingers.hashnode.dev/toktumi-1-en"><strong>see Part 1</strong></a>.</p>
<h1>1. Why Train the Model from Scratch Again?</h1>
<p>I decided to <strong>train a new model from scratch</strong>. The goal was to implement two key recommendations:</p>
<ol>
<li><strong>Remove internet comments</strong> from the training dataset, which I expected would eliminate chaotic and low-quality content.</li>
<li>Add <strong>Question–Answer (QA)</strong> and <strong>Instruction–Answer (IA)</strong> datasets to help the model learn how to interact with users and generate coherent responses to specific prompts and instructions.</li>
</ol>
<p><strong>Fine-Tuning or Training from Scratch Again?</strong></p>
<p>Instead of <strong>fine-tuning</strong> (continuing the training of an already trained model), I decided to <strong>train a new model entirely from scratch</strong>. I was concerned that fine-tuning would not eliminate the problems caused by the original training dataset. Training from scratch makes it possible to build a model using only a cleaned and properly prepared text corpus, providing greater control over the quality of the result.</p>
<p>Since I had already completed the entire model-training process once, <strong>the second iteration was simpler and less time-consuming</strong>. Many steps could be repeated with only minor modifications, and changes were needed only where necessary. As before, the biggest challenge was <strong>preparing new texts for training the model</strong>, this time in the form of question–answer and instruction–answer pairs.</p>
<h1>2. Planning the Next Stages</h1>
<blockquote>
<p>2a. <strong>Collect new data</strong> - prepare <strong>Question–Answer (QA)</strong> and <strong>Instruction–Answer (IA)</strong> datasets.</p>
<p>2b. <strong>Prepare the complete training dataset.</strong></p>
<p>2c. <strong>Train from scratch</strong> - carry out a full training run using the <strong>GPT-2 Medium</strong> architecture and the improved text dataset.</p>
<p>2d. <strong>Evaluate model quality</strong> - stage 1 - measure checkpoint perplexity.</p>
<p>2e. <strong>Evaluate model quality</strong> - stage 2 - prompting and human evaluation.</p>
<p>2f. <strong>Finish training at the selected checkpoint</strong>.</p>
</blockquote>
<h2>2a. Data Collection. Question–Answer (QA) and Instruction–Answer (IA) Datasets</h2>
<p><strong>I started this stage in early 2025</strong>. At the time, I could not find extensive instruction datasets in Polish online, although such datasets existed in other languages, mainly English. Examples included <a href="https://huggingface.co/datasets/databricks/databricks-dolly-15k">databricks-dolly-15k</a> (15,000 examples, English), <a href="https://huggingface.co/datasets/HuggingFaceH4/helpful-instructions">HuggingFaceH4 helpful-instructions</a> (150,000 examples, English), <a href="https://huggingface.co/datasets/laion/OIG">OIG: Open Instruction Generalist</a> (44 million examples, English), and <a href="https://huggingface.co/datasets/CohereLabs/aya_collection">Aya Dataset</a> (513 million examples in over 100 languages, but relatively few in Polish).</p>
<p>In this situation, an attractive option was to <strong>generate such data myself</strong>, using existing LLMs. This approach is often referred to as generating <strong>synthetic data</strong>. More on this topic: <a href="https://arxiv.org/pdf/2503.14023">[Article]</a>.</p>
<p>I decided to carry out <strong>two experiments</strong>:</p>
<ul>
<li>Generate data <strong>locally</strong> on my home PC using <a href="https://huggingface.co/mistralai/Mistral-Nemo-Instruct-2407"><strong>Mistral-Nemo-12B-Instruct-2407</strong></a>, downloaded from Hugging Face, with prompts managed through Python.</li>
<li>Generate data using the <strong>Gemini API</strong> (Google's model). This allowed me not only to collect data, but also to practice integrating Python with an external API and compare the quality of the results. I used <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash"><strong>gemini-2.5-flash</strong></a>.</li>
</ul>
<p>Both approaches had their <strong>advantages and limitations</strong>. Running Mistral locally gave me full control over the process and made me independent of external services. The downside was the <strong>hardware limitations</strong> of my GeForce RTX 3060, which meant <strong>long generation times</strong> and <strong>significant energy consumption</strong> - greater than during the model training itself.</p>
<p>Gemini, on the other hand, runs in the cloud. It is faster, easier to use, and produces more polished responses in Polish. However, this approach also has its <strong>limitations and API-imposed quotas</strong>, which made large-scale text generation difficult. Paid access was an alternative, but at this scale the costs would have been too high for the project's <strong>limited budget</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/3f84327b-2c14-4bfe-8364-028f9d4a6aa8.png" alt="024_QA_Mistral" style="display:block;margin:0 auto" />

<hr />
<p><strong>INSTRUCTION DATASETS GENERATED LOCALLY (MISTRAL NEMO 12B)</strong></p>
<p>To generate Question–Answer (QA) and Instruction–Answer (IA) datasets, I used <strong>Mistral-Nemo-12B-Instruct-2407</strong>, running locally on my home PC.</p>
<p><strong>How the code works:</strong> My working directory contained <strong>text files</strong> (.txt) with articles on various topics, previously collected from <a href="http://fakt.pl"><strong>fakt.pl</strong></a> and <a href="http://polsatnews.pl"><strong>polsatnews.pl</strong></a>. I passed each text to the model together with a detailed <strong>prompt</strong> specifying the required output format (<strong>questions and instructions with their answers</strong>). This was also an interesting exercise in <strong>prompt engineering</strong>. With Mistral 12B, refining the prompt was crucial - only extensive testing produced satisfactory results.</p>
<p>Along with the prompt, the model receives the following instructions:</p>
<ul>
<li>generate up to five questions with different structures and lengths, together with their answers,</li>
<li>limit closed-ended "yes/no" questions (the model tended to overuse this format),</li>
<li>generate different types of instructions related to the article,</li>
<li>use only correct, fluent Polish,</li>
<li>avoid additional comments and formatting marks (such as asterisks or hyphens), although Mistral still inserted them occasionally.</li>
</ul>
<p>This process made it possible to automatically generate ready-to-use sets of text pairs. Below is a section of the code used to process a sample article.</p>
<p>Example: The original text being converted into a set of questions and instructions.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d8460a5d-c946-41db-9c94-ce171f5962f2.png" alt="022_text_Adam_West" style="display:block;margin:0 auto" />

<p>The code used to generate these datasets reads an article, passes it to the model along with the prepared prompt, and then generates responses. Due to hardware limitations, I also used <strong>4-bit quantization</strong>. More on <a href="https://huggingface.co/docs/transformers/main/en/perf_infer_gpu_one?bnb=4-bit#model-quantization">quantization</a>.</p>
<pre><code class="language-python">import random
import torch
from time import time
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

# Model i plik źródłowy
model_id = "Mistral-Nemo-12B-Instruct-2407"
input_file = "sample.txt"

# Kwantyzacja 4-bitowa
quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4"
)

# Wczytanie modelu i tokenizera
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    device_map="auto",
    quantization_config=quant_config
).eval()

# Wczytanie artykułu
with open(input_file, "r", encoding="utf-8") as f:
    text = f.read().strip()

# Losowanie czasowników do przykładów instrukcji
verbs = [
    "Opisz", "Streść", "Wyjaśnij", "Wymień", "Znajdź",
    "Podaj", "Napisz", "Wylicz", "Przeanalizuj",
    "Określ", "Uzasadnij", "Przedstaw", "Oceń",
    "Scharakteryzuj", "Wskaż", "Zdefiniuj", "Porównaj",
    "Zaproponuj", "Uporządkuj", "Powiedz", "Opowiedz"
]

random.shuffle(verbs)
examples = ", ".join(verbs[:7])

# Prompt — pełna treść przedstawiona poniżej
prompt_template = """treść promptu"""
prompt = prompt_template.format(examples=examples, text=text)

# Przygotowanie wejścia dla modelu
messages = [
    {"role": "system", "content": ""},
    {"role": "user", "content": prompt.strip()}
]

chat_input = tokenizer.apply_chat_template(
    messages,
    add_generation_prompt=True,
    return_tensors="pt"
).to(model.device)

# Generowanie pytań, instrukcji i odpowiedzi
start_time = time()

with torch.inference_mode():
    outputs = model.generate(
        chat_input,
        max_new_tokens=1024,
        do_sample=True,
        temperature=0.4,
        top_p=0.7
    )

# Dekodowanie samej wygenerowanej odpowiedzi
generated_ids = outputs[0][chat_input.shape[-1]:]
result = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()

print(result)
print(f"Czas generowania: {time() - start_time:.2f} s")
</code></pre>
<p><strong>The key element is the prompt itself (written in Polish, which Mistral can handle):</strong></p>
<blockquote>
<p>Zadaj maksymalnie 5 pytań, na które odpowiedzi znajdują się w treści. Pytania muszą być różnorodne pod względem struktury i długości. Nie wolno zadawać więcej niż jednego pytania, które zaczyna się od „Czy”. Nie zadawaj więcej niż jednego pytania, którego odpowiedź mogłaby być „tak” lub „nie”. Zabronione jest powtarzanie pytań. Pod pytaniem zawsze napisz odpowiedź. Odpowiedź ma być tak szczegółowa, jak to potrzebne.</p>
<p>Następnie stwórz kilka przykładowych tekstów instrukcji w kontekście treści tego tekstu (format imperativus, np. {examples}). To tylko przykłady, wybierz takie, które najbardziej będą pasować do tekstu. Staraj się, aby teksty instrukcji były różnorodne i różniły się między sobą. Zabronione jest powtarzanie instrukcji. Pod instrukcją zawsze napisz odpowiedź. Odpowiedzi powinny być różnorodne.</p>
<p>Uwagi do całego tekstu, który stworzysz: Pisz tylko i wyłącznie w języku polskim. Pisz płynną, poprawną polszczyzną. Nie oznaczaj tekstu dodatkowymi komentarzami od siebie. Nigdy nie stosuj gwiazdek '*', '**' ani myślników '-' w celu wyróżnienia tekstu. Chcę mieć czysty tekst gotowy do użycia: pytanie-odpowiedź, instrukcja-odpowiedź. Nie dodawaj nic więcej. Zapisz wszystko jako ciąg pytań i odpowiedzi oddzielonych pustymi liniami.</p>
<p>Tekst: {text}</p>
</blockquote>
<p><strong>English translation of the prompt:</strong></p>
<blockquote>
<p>Ask a maximum of 5 questions whose answers can be found in the text. The questions must vary in structure and length. Do not ask more than one question beginning with “Czy”. Do not ask more than one question that could be answered with “yes” or “no”. Repeating questions is prohibited. Always write the answer below each question. The answer should be as detailed as necessary.</p>
<p>Then create several examples of instructions related to the text (imperative form, e.g. {examples}). These are only examples; choose those that best fit the text. Try to make the instructions varied and different from one another. Repeating instructions is prohibited. Always write an answer below each instruction. The answers should be varied.</p>
<p>Notes for the entire text you produce: Write only in Polish. Use fluent, correct Polish. Do not add any comments or labels of your own. Never use asterisks '*', '**' or hyphens '-' to emphasize text. I want clean text ready to use: question–answer, instruction–answer. Do not add anything else. Write everything as a sequence of questions and answers separated by blank lines.</p>
<p>Text: {text}</p>
</blockquote>
<p>The prompt had to include these details because, as the tests showed, the model tended to take the path of least resistance and generate repetitive structures, often beginning with <strong>“Czy”</strong>. That is why I limited the number of such question forms in the prompt.</p>
<p>In addition, when generating instructions, I used a <strong>simple trick</strong> involving the {examples} variable. The model tended to produce monotonous, repetitive instructions. I selected <strong>21 verbs</strong> that worked best for this task. For each article, I then randomly selected 7 of them and supplied them to the model as examples in the prompt. Tests showed that this was <strong>the most effective way to increase the variety of generated instructions</strong>, both with the locally run Mistral model and with the Gemini API.</p>
<p>The 21 Polish verbs (translated into English) were: <strong>Describe, Summarize, Explain, List, Find, Provide, Write, Enumerate, Analyze, Specify, Justify, Present, Evaluate, Characterize, Identify, Define, Compare, Suggest, Organize, Say, Tell me about.</strong></p>
<h3>MISTRAL – RESULTS:</h3>
<p><strong>The code successfully converted the source text</strong> into a set of questions and instructions with corresponding answers. However, generation was <strong>relatively time-consuming</strong> — on my graphics card, it took an average of about <strong>1 minute per article</strong>.</p>
<p>With <strong>4-bit quantization</strong>, the generated texts occasionally contained minor linguistic errors, such as “Kto był Adam West?” instead of “Kim był Adam West?”. (Both translate as “Who was Adam West?” in English, but the first uses the wrong grammatical form in Polish.) Such constructions occurred relatively often with the local Mistral model, affecting the quality of the training dataset. Despite this, the method served its purpose well.</p>
<p>The Mistral test was <strong>a valuable experiment</strong>, demonstrating both the potential and the limitations of working with large <strong>locally run models</strong>.</p>
<hr />
<p><strong>INSTRUCTION DATASETS GENERATED ONLINE USING THE GEMINI API (GOOGLE)</strong></p>
<p>For the second experiment, I used <strong>Gemini 2.5 Flash</strong>, a model developed by <strong>Google</strong> that was popular at the time (2025) and accessible through an <strong>API</strong>. This approach allows the model to be integrated with Python and text generation to be automated, including batch processing of many files.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/816738f1-3629-44cf-84dd-33a9b6e751ea.png" alt="025_QA_Gemini" style="display:block;margin:0 auto" />

<p>As with Mistral, I converted source articles into sets of questions and instructions with answers. The difference was that <strong>all the computational work was handled by the service provider</strong>.</p>
<p><strong>Gemini 2.5 Flash proved significantly more effective than the locally run Mistral 12B</strong>. It generated texts in <strong>more fluent Polish</strong>, interpreted instructions better, and followed the required response format more consistently.</p>
<h3>GEMINI – RESULTS:</h3>
<p>Gemini 2.5 Flash generated content <strong>more than twice as fast</strong> (around 30 seconds per article) and <strong>at a higher quality</strong>. It also handled <strong>more complex instructions</strong> more effectively, such as inserting delimiters.</p>
<p><strong>Using an API</strong>, however, has its <strong>limitations</strong>, especially restrictions on the number of requests and generated tokens. When processing large numbers of articles, this was a significant obstacle that did not arise with the locally run model.</p>
<p>Ultimately, <strong>I generated most of the texts locally using the Mistral model</strong>.</p>
<p>Example: An excerpt from the training dataset. Cleaned question–answer (QA) and instruction–answer (IA) pairs, ready for training.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/08d60ba1-40d8-4822-a793-1f7970b5f386.png" alt="023_pytania_clean" style="display:block;margin:0 auto" />

<p>After more than <strong>40 days</strong> of generating data in both environments (local and cloud-based), I managed to collect <strong>616 279 text pairs</strong> in the form of <strong>Question–Answer (340 447)</strong> and <strong>Instruction–Answer (275 832)</strong> records.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0c235a1f-5e6b-4f08-9dad-b2e177e4751b.png" alt="027_instr_w_liczbach" style="display:block;margin:0 auto" />

<p><strong>Generating synthetic data at scale is costly and time-consuming</strong> — it requires substantial computing power, whether using a local GPU or an API service.</p>
<p><strong>Web scraping is much faster and cheaper</strong>, but it does not always provide texts of the required quality and structure.</p>
<p>Another option would have been <strong>automatically translating existing instruction datasets from English</strong>. I decided against this approach, however, because I considered the results of my experiments so far sufficient.</p>
<h2>2b. Preparing the New Training Dataset</h2>
<p>For the new training dataset, I reused texts previously collected from the following sources: <strong>Wikipedia, Pudelek, Polsat News, books, and</strong> <strong>Fakt</strong>. However, I excluded <strong>Wykop and jokes</strong>, which, as earlier tests had shown, negatively affected the quality of the model's responses. The model would often respond in the style of internet comments or produce answers that had little connection to reality.</p>
<p>In addition, I used <strong>web scraping to collect 67 846 new articles</strong> from Fakt, published <strong>up to August 12, 2025</strong> (the <strong>cutoff date</strong> for this dataset). The purpose was to update the model's knowledge of current events, such as <strong>Donald Trump</strong> taking office as President of the United States and <strong>Karol Nawrocki</strong> becoming President of Poland. The tests showed how important it is to <strong>keep training data up to date</strong>. This is one of the challenges facing language model developers. An LLM's knowledge needs to keep pace with a constantly changing world; otherwise, its answers about current events quickly become outdated.</p>
<p>The most important change, however, was adding <strong>616 279 synthetically generated question–answer and instruction–answer pairs</strong> to the training dataset, prepared during the earlier experiments with Mistral and Gemini.</p>
<p>The result was a <strong>hybrid training dataset</strong> combining <strong>original source texts</strong> with <strong>synthetically generated question–answer and instruction–answer pairs</strong>. This allowed the model to learn from both natural texts and examples of direct interaction with a user.</p>
<p>Sources and text datasets used to train Toktumi 2.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/28bce664-73f4-4703-a1ec-3b26b9498086.png" alt="026_teksty_model2" style="display:block;margin:0 auto" />

<p>Despite adding a large instruction dataset, removing the Wykop data (previously the largest category of texts) meant that the training dataset <strong>shrank</strong> by approximately <strong>25%</strong> in terms of the number of text samples and by approximately <strong>40%</strong> in terms of characters and tokens.</p>
<p>As it later turned out, this step also contributed <strong>to improving the quality of Toktumi 2.0</strong>.</p>
<p>Comparison of training dataset sizes: Toktumi 1.0 | Toktumi 2.0.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/3d858b07-8311-4371-8ef8-0d091bc5e828.png" alt="047_zbiory_treningowe_T1-T2" style="display:block;margin:0 auto" />

<h2>2c. Training the Model</h2>
<p>Next, I followed the same steps as when training Toktumi 1.0:</p>
<blockquote>
<ul>
<li><strong>I reduced the character set to 256 characters</strong> (by converting UTF-8 → ANSI → UTF-8).</li>
<li>I separated text samples using the <strong>delimiter</strong> <code>&lt;|endoftext|&gt;</code>.</li>
<li>I <strong>set aside a validation file</strong> from the training data (a text file of approximately 10 MB).</li>
<li>I prepared a <strong>new tokenizer</strong> based on the new training dataset (vocab_size = 50 000). It is important to understand the consequences of this step: each tokenizer has its own vocabulary and token IDs, so the new tokenizer was not compatible with the Toktumi 1.0 tokenizer. This was a valuable lesson — from that point onward, <strong>I kept the Toktumi 2.0 tokenizer for all subsequent stages of model development</strong>.</li>
<li>I created an <strong>empty GPT-2 Medium model</strong>, using the same method as before.</li>
<li>I <strong>started training</strong>, this time setting the number of <strong>epochs to 8</strong> (bearing in mind that Toktumi 1.0 training had ended at around 6.44 epochs).</li>
</ul>
</blockquote>
<p>Training <strong>Toktumi 2.0</strong> on a smaller dataset and with fewer planned epochs meant that the entire process involved <strong>829 048 steps</strong>, and completing <strong>8 epochs</strong> took approximately <strong>11.5 days</strong> (excluding technical interruptions for restarting the computer and resuming training from checkpoints) — compared with <strong>approximately 15 days (6.44 epochs)</strong> for <strong>Toktumi 1.0</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a11cdfcd-7bd1-4293-a46d-4dc0e58f2444.png" alt="028_epoki_Toktumi2" style="display:block;margin:0 auto" />

<h2>2d. Model Evaluation. Stage 1: Perplexity.</h2>
<p>As in the first stage of the project, <strong>I evaluated the model in two stages</strong>. First, I measured <strong>perplexity</strong> for successive checkpoints using the evaluation code I had prepared earlier. The charts below show the results for both models.</p>
<p>The learning curves of the two models differ somewhat. For <strong>Toktumi 1.0</strong>, perplexity decreased gradually, reaching <strong>approximately 26</strong>. In contrast, <strong>Toktumi 2.0</strong> showed a sharper decline and quickly stabilized <strong>at around 19</strong>. This pattern suggests that <strong>Toktumi 2.0</strong> achieved a better fit to its data and produced <strong>more consistent predictions</strong>.</p>
<p>However, it is important to remember that the two models were evaluated on different validation datasets, so the comparison should be treated as <strong>approximate</strong>.</p>
<p>Comparison of perplexity in Toktumi 1.0 and Toktumi 2.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0c12714a-2117-4456-838c-2eb6a7821910.png" alt="030_perplexity_porównanie" style="display:block;margin:0 auto" />

<h2>2e. Model Evaluation. Stage 2: Prompting.</h2>
<p><strong>The second stage of evaluation</strong> involved testing Toktumi 2.0 on a <strong>set of 150 prompts</strong>, the same ones I had previously used to evaluate Toktumi 1.0 (<a href="https://sixfingers.hashnode.dev/toktumi-1-en">see Part 1</a>).</p>
<p>I automated the testing process with Python code that ran the prompts on successive checkpoints and saved the responses. I then <strong>evaluated the results manually</strong>.</p>
<p>Because of the large number of responses, I also used AI to help analyze the results. The assessments were not always clear-cut, but after several rounds of comparison, I was able to identify the best checkpoint.</p>
<p>As before, I used <strong>greedy decoding (top_k = 1)</strong>, which ensured that the model always generated the same, most probable response. This made it possible to <strong>keep the testing conditions consistent</strong>.</p>
<p>The full list of 150 prompts and model responses -&gt; <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_2.0_150_prompts_checkpoint-787500.txt"><strong>VIEW</strong></a>.</p>
<h2>2f. Ending the Training Run</h2>
<p>Ultimately, I selected <strong>checkpoint-787500</strong>, saved at around <strong>7.60 epochs</strong>, as the best checkpoint.</p>
<p>I used it to prepare the <strong>final Toktumi 2.0 model</strong>, together with its tokenizer.</p>
<h1>3. Final Model Evaluation</h1>
<p>In the final step, I carried out a <strong>final evaluation and comparison of Toktumi 1.0 and Toktumi 2.0</strong>. To ensure a consistent comparison, I developed and applied <strong>a single evaluation methodology</strong>. I classified each response in two steps:</p>
<h3><strong>STEP 1 — Instruction Following (IF)</strong></h3>
<p><strong>Does the model correctly recognize the structure of the prompt (Instruction Following, IF)?</strong></p>
<p>For example, when given a question, does it attempt to answer it as a question? When given an instruction, does it respond appropriately to the instruction? And when given an incomplete sentence, does it complete that sentence?</p>
<p>Toktumi 1.0 example:</p>
<blockquote>
<p><strong>Prompt 05:</strong> <em>Wymień trzy warzywa.</em> [EN: <em>Name three vegetables.</em>]</p>
<p><strong>Answer:</strong> <em>Wymień trzy warzywa. Te z nich są najbardziej wymagające.</em> [EN: <em>Name three vegetables. These are the most demanding ones.</em>]</p>
</blockquote>
<p>In this example, Toktumi 1.0 failed to recognize the structure of the instruction. Instead of following it and naming three vegetables, it continued the text as though extending the prompt. I classified such responses as <strong>NO</strong>.</p>
<p>Toktumi 2.0 example:</p>
<blockquote>
<p><strong>Prompt 05:</strong> <em>Wymień trzy warzywa.</em> [EN: <em>Name three vegetables.</em>]</p>
<p><strong>Answer:</strong> <em>- Ziemniaki, pomidory i ogórki. (np. ziemniaki, papryka, cebula).</em> [EN: <em>- Potatoes, tomatoes, and cucumbers. (e.g., potatoes, bell peppers, onions).</em>]</p>
</blockquote>
<p>In this example, Toktumi 2.0 correctly recognized the instruction and named three vegetables. I labeled such responses <strong>YES</strong>.</p>
<h3><strong>STEP 2 — Factuality (FCT)</strong></h3>
<p>In the next step, I checked <strong>whether the answer was correct, factually accurate, and made sense (Factuality, FCT).</strong></p>
<p>I classified responses containing <strong>hallucinations, false information, or internal contradictions</strong> negatively, marking them as <strong>HAL</strong>.</p>
<p>I applied <strong>lenient evaluation criteria</strong> — partially correct responses could also receive a <strong>YES</strong> rating if the key part of the answer was correct.</p>
<p>Toktumi 1.0 example:</p>
<blockquote>
<p><strong>Prompt 13:</strong> <em>Mężczyzna to osoba płci</em> [EN: <em>A man is a person of the ... sex.</em>]</p>
<p><strong>Answer:</strong> <em>Mężczyzna to osoba płci żeńskiej.</em> [EN: <em>A man is a person of the female sex.</em>]</p>
</blockquote>
<p>In this example, Toktumi 1.0 recognized the language structure and completed the sentence / gave an answer (STEP 1 → YES). However, the answer was factually incorrect. I labeled such responses <strong>HAL</strong>. For simplicity, I include them in the same category as hallucinations.</p>
<p>Toktumi 2.0 example:</p>
<blockquote>
<p><strong>Prompt 13:</strong> <em>Mężczyzna to osoba płci</em> [EN: <em>A man is a person of the ... sex.</em>]</p>
<p><strong>Answer:</strong> <em>Mężczyzna to osoba płci męskiej, która nie jest związana z żadną partią polityczną.</em> [EN: <em>A man is a person of the male sex who is not affiliated with any political party.</em>]</p>
</blockquote>
<p>In this example, Toktumi 2.0 correctly recognized the language structure and completed the sentence / gave an answer (STEP 1 → YES). In addition, the answer was consistent with real-world knowledge in the part about sex, while I treated the extra statement about having no political affiliation as incidental and not significantly affecting the correctness rating. I took a lenient approach, focusing on the most important parts of the response rather than making the criteria overly strict. I also classified such <strong>partially correct</strong> responses as <strong>YES</strong>.</p>
<p>Toktumi 1.0 example:</p>
<blockquote>
<p><strong>Prompt 62:</strong> <em>Czekolada jest</em> [EN: <em>Chocolate is</em>]</p>
<p><strong>Answer:</strong> <em>Czekolada jest zdrowa, ale niezdrowa.</em> [EN: <em>Chocolate is healthy, but unhealthy.</em>]</p>
</blockquote>
<p>In this case, Toktumi 1.0 gave an internally contradictory answer — it contained two mutually exclusive statements. I treated this type of response as an example of hallucination and also labeled it <strong>HAL</strong>.</p>
<p>This is how I arrived at a comparison of the response quality of both models.</p>
<h3>Toktumi 1.0 Results</h3>
<table>
<thead>
<tr>
<th>Classification [IF / FCT]</th>
<th>Number of cases</th>
</tr>
</thead>
<tbody><tr>
<td><strong>YES / YES</strong> - correct structure and correct answer</td>
<td>85</td>
</tr>
<tr>
<td><strong>YES / HAL</strong> - correct structure, but a hallucination</td>
<td>33</td>
</tr>
<tr>
<td><strong>NO</strong> - failure to recognize the instruction correctly</td>
<td>32</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td>150</td>
</tr>
</tbody></table>
<h3>Toktumi 2.0 Results</h3>
<table>
<thead>
<tr>
<th>Classification [IF / FCT]</th>
<th>Number of cases</th>
</tr>
</thead>
<tbody><tr>
<td><strong>YES / YES</strong> - correct structure and correct answer</td>
<td>102</td>
</tr>
<tr>
<td><strong>YES / HAL</strong> - correct structure, but a hallucination</td>
<td>42</td>
</tr>
<tr>
<td><strong>NO</strong> - failure to recognize the instruction correctly</td>
<td>6</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td>150</td>
</tr>
</tbody></table>
<h3>FINDINGS</h3>
<ul>
<li><strong>Toktumi 2.0</strong> produces <strong>higher-quality responses</strong> than version <strong>1.0</strong>.</li>
</ul>
<p><strong>Conclusion:</strong> Training a new model from scratch was the right decision, and the improvements I introduced led to a clear increase in response quality.</p>
<ul>
<li>The greatest improvement was in <strong>instruction following (IF)</strong>. The number of cases in which the model correctly recognized the instruction rose from 118 to 144. At the same time, the number of correct answers increased from 85 to 102, an improvement of approximately <strong>20%</strong>.</li>
</ul>
<p><strong>Conclusion:</strong> Training Toktumi 2.0 on <strong>Question–Answer (QA)</strong> and <strong>Instruction–Answer (IA)</strong> datasets was effective and improved its ability to recognize and follow instructions.</p>
<ul>
<li>Toktumi 2.0 <strong>greatly reduced the problem of responses resembling internet comments</strong> — the model was less likely to imitate chaotic, judgmental user language and much more likely to <strong>act like a friendly assistant</strong>.</li>
</ul>
<p><strong>Conclusion:</strong> Removing Wykop comments from the training dataset was the right decision and improved the quality of generated responses. This shows that <strong>the quality of training data can matter more than its quantity</strong>.</p>
<h3>ADDITIONAL OBSERVATIONS</h3>
<p><strong>Toktumi 2.0 handles instruction-style prompts significantly better than version 1.0.</strong></p>
<p>[Toktumi 1.0]</p>
<blockquote>
<p><strong>Prompt 08:</strong> <em>Podaj przykład emocji.</em> [EN: <em>Give an example of an emotion.</em>]</p>
<p><strong>Answer:</strong> <em>Komentarz: Nie ma to jak wmawiać ludziom, że są lepsi od innych.</em> [EN: <em>Comment: Nothing beats convincing people that they are better than others.</em>]</p>
</blockquote>
<p>[Toktumi 2.0]</p>
<blockquote>
<p><strong>Prompt 08:</strong> <em>Podaj przykład emocji.</em> [EN: <em>Give an example of an emotion.</em>]</p>
<p><strong>Answer:</strong> <em>Przykładem emocji może być radość, radość lub smutek, gdy ktoś jest szczęśliwy i cieszy się z tego, co ma.</em> [EN: <em>An example of an emotion could be joy, joy or sadness, when someone is happy and enjoys what they have.</em>]</p>
</blockquote>
<p>Of the 14 instructions in the test set (such as "give..." or "name..."), <strong>Toktumi 1.0</strong> responded correctly (IF=YES) in only <strong>1 out of 14 cases</strong>, while <strong>Toktumi 2.0</strong> did so in <strong>13 out of 14</strong>.</p>
<p><strong>In other words:</strong> in terms of instruction following, <strong>Toktumi 2.0</strong> shows <strong>enormous progress in recognizing and carrying out instructions</strong>, compared with Toktumi 1.0's almost complete inability to do so.</p>
<p><strong>Both models still struggled with factual knowledge</strong>, even when asked very simple questions.</p>
<p>The following pencil example illustrates this well.</p>
<p>[Toktumi 2.0]</p>
<blockquote>
<p><strong>Prompt 91:</strong> <em>Do czego służy ołówek?</em> [EN: <em>What is a pencil used for?</em>]</p>
<p><strong>Answer:</strong> <em>Ołówek to jeden z najpopularniejszych przedmiotów codziennego użytku. Jest niezbędny do prawidłowego funkcjonowania organizmu, a także do pracy fizycznej i psychicznej. Jego używanie jest bardzo proste, jednak nie każdy wie, jak się do tego zabrać. Jak się go pozbyć?</em> [EN: <em>A pencil is one of the most common everyday objects. It is essential for the proper functioning of the body, as well as for physical and mental work. Using it is very simple, but not everyone knows how to go about it. How do you get rid of it?</em>]</p>
</blockquote>
<p>Interestingly, the Wikipedia definition of the word "pencil" was present in the training datasets of both models. Even so, the model was unable to answer such a simple question correctly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/4779662f-ba86-4353-98f5-41025e0fe1b5.png" alt="033_ołówek_wikipedia" style="display:block;margin:0 auto" />

<p><strong>Experiment:</strong> After increasing the <code>top_k</code> parameter from 1 to 2 and introducing an element of randomness, the model generated an answer much closer to the expected definition.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Do czego służy ołówek?</em> [EN: <em>What is a pencil used for?</em>]</p>
<p><strong>Answer:</strong> <em>Ołówki to bardzo praktyczne i uniwersalne narzędzia do pisania. Są one nie tylko ozdobą, ale także ułatwiają pisanie tekstów oraz ich czytanie w różnych miejscach na świecie - od Londynu po Los Angeles czy Chicago.</em> [EN: <em>Pencils are very practical and versatile writing tools. They are not only decorative objects, but also make it easier to write and read texts in various places around the world - from London to Los Angeles or Chicago.</em>]</p>
</blockquote>
<p><strong>Conclusion:</strong> The mere presence of information in the training dataset does not guarantee that the model will select it as the most obvious answer to a prompt (in greedy decoding mode). The experiment also showed how strongly <strong>generation hyperparameters and prompting techniques</strong> can influence the responses produced.</p>
<h1>SUMMARY</h1>
<p>In this part, I described <strong>the second stage of Toktumi's development (Toktumi 2.0)</strong>, in which I applied the experience and lessons learned from the first training run (Toktumi 1.0). The most important change was <strong>rebuilding the training dataset</strong> — removing problematic internet comments and introducing <strong>616 279 synthetically generated question–answer and instruction–answer pairs</strong>, prepared using the Mistral and Gemini models.</p>
<p>The result was a <strong>hybrid training dataset</strong> containing <strong>2 336 162 text samples</strong>, combining natural source texts with examples of interaction with a user. I used it to create a new tokenizer and <strong>train a new GPT-2 Medium model from scratch</strong>, on my home graphics card.</p>
<p><strong>The results confirmed that the changes were moving in the right direction</strong> — in the 150-prompt test, the number of correct answers rose from <strong>85 to 102</strong>, and the number of correctly recognized instructions increased from <strong>118 to 144</strong>.</p>
<p>The experiment demonstrated the importance of <strong>the quality and structure of training data</strong>, rather than just its quantity. Despite having a smaller text dataset, Toktumi 2.0 handled instructions much better and was less likely to imitate the chaotic style of internet comments.</p>
<p><strong>The biggest unresolved problem was factual knowledge.</strong> The model still frequently hallucinated and struggled to give correct answers even to simple questions. I therefore decided it was worth taking the next step — rather than training another model from scratch, <strong>continuing to train Toktumi 2.0 on a new, synthetically prepared dataset of short questions, instructions, and dialogues</strong>.</p>
<p>You can read more about the model's development in <a href="https://sixfingers.hashnode.dev/toktumi-3-en">Part 3</a>.</p>
<h2>Toktumi 2.0 on Hugging Face Spaces</h2>
<p>You can try <strong>Toktumi 2.0</strong> on <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-2.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-2.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/7c04669e-ef0c-496a-b2e3-fe5f53175962.png" alt="52_T2_interface" style="display:block;margin:0 auto" />

<p><a href="https://sixfingers.hashnode.dev/toktumi-3-en"><strong>NEXT PART</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[How to Build a Large Language Model from Scratch? Toktumi 1.0 – Part 1/3]]></title><description><![CDATA[Toktumi 1.0: a Polish GPT-2 Medium trained from scratch. Web scraping, 3.1M text samples, a custom tokenizer and training on a home GPU.]]></description><link>https://sixfingers.hashnode.dev/toktumi-1-en</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-1-en</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[web scraping]]></category><category><![CDATA[pytorch]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 21:11:19 GMT</pubDate><content:encoded><![CDATA[<h2><strong>Project Overview</strong></h2>
<p>As a personal project, I decided to build and train an <strong>LLM from scratch</strong>, designed to operate <strong>in Polish</strong>. The project is <strong>educational, research-oriented, and non-commercial</strong>, and its main goal was to gain hands-on experience in building and training language models.</p>
<p>I started the project in 2023. I chose the <strong>GPT-2</strong> architecture developed by OpenAI. It was one of the groundbreaking language models that <strong>played an important role in the development of generative artificial intelligence</strong>. With its code and model weights publicly available, it was a good starting point for experiments at the time.</p>
<p>The idea was to <strong>build a similar model from start to finish</strong>, although on a smaller scale. This meant independently <strong>collecting texts</strong> (web scraping), <strong>cleaning the data</strong>, preparing a <strong>large training dataset</strong>, developing <strong>optimized training code</strong>, completing the <strong>training process on a home PC</strong>, and then finding <strong>the most effective prompting methods</strong> for the finished model.</p>
<p>I used the <strong>Python</strong> programming language and the <strong>PyTorch</strong> and <strong>Transformers</strong> libraries throughout the project.</p>
<p>The project was ultimately completed <strong>in three stages</strong>:</p>
<blockquote>
<p><strong>Stage 1 - Toktumi 1.0 (2024):</strong> I built and trained the first working version of the model <strong>from scratch</strong>. I used <strong>3 115 002 text samples</strong> collected from various online sources and other materials. I prepared my own tokenizer, carried out the training, and performed the first tests of response quality.</p>
</blockquote>
<blockquote>
<p><strong>Stage 2 - Toktumi 2.0 (2025):</strong> I trained a new model <strong>from scratch</strong>, using an improved training dataset containing <strong>2 336 162 text samples</strong>. Based on the experience and lessons learned from the previous stage, I modified both the structure and the content of the training dataset.</p>
</blockquote>
<blockquote>
<p><strong>Stage 3 - Toktumi 3.0 (2026):</strong> I <strong>fine-tuned</strong> Toktumi 2.0 on an entirely synthetic dataset of <strong>6 190 420 shorter records</strong>, consisting exclusively of <strong>question–answer pairs, instruction–answer pairs</strong>, and <strong>simple dialogue pairs</strong>. The goal was to improve the model's factual knowledge, expand its language capabilities, and enhance the quality of its generated responses.</p>
</blockquote>
<p>I describe each of the three stages in detail <strong>in the following parts of this series</strong>.</p>
<h1><strong>CHAPTER I. Toktumi 1.0. First Training Run.</strong></h1>
<h1>1. Choosing the Model Size</h1>
<p>Of the four available <strong>GPT-2 model sizes (XL, Large, Medium, Small)</strong>, I chose <strong>Medium</strong> because it offered a reasonable compromise between my hardware resources and the quality of the generated text. Another limiting factor was the amount of VRAM (12 GB) available on my NVIDIA RTX 3060 graphics card. Training GPT-2 Medium with my chosen configuration used approximately 11 GB. I considered this the optimal choice.</p>
<p><strong>Available GPT-2 model sizes</strong></p>
<ul>
<li>GPT-2 Small: 124M, or 124 million parameters</li>
<li><strong>GPT-2 Medium: 345M, or 345 million parameters</strong> &lt;&lt; selected model</li>
<li>GPT-2 Large: 762M, or 762 million parameters</li>
<li>GPT-2 XL: 1.5B, or 1.5 billion parameters</li>
</ul>
<h1>2. Analyzing OpenAI's Approach (GPT-2)</h1>
<p>I began by reviewing the available information on <strong>GPT-2</strong>, published by OpenAI on <a href="https://huggingface.co/openai-community/gpt2">Hugging Face</a> and <a href="https://github.com/openai/gpt-2/tree/master">GitHub</a>, to learn from the approaches used by the model's creators.</p>
<p>Some background: the original <strong>GPT-2 model, introduced by OpenAI in February 2019</strong>, was trained on a dataset of approximately <strong>40 GB</strong>, called <strong>WebText</strong> <a href="https://openai.com/index/better-language-models/">[source: OpenAI]</a>, <a href="https://huggingface.co/openai-community/gpt2">[source: Hugging Face]</a>. This dataset contained approximately <strong>8 million text samples</strong> collected from websites. A partial list of those URLs was published on <a href="https://github.com/openai/gpt-2/blob/master/model_card.md">GitHub</a>. However, the company did not release the dataset itself. The available sources still provide some insight into its contents and the methodology used by OpenAI.</p>
<p>I also drew inspiration from Polish-language GPT-2 models available at the time, developed by <strong>Sławomir Dadas</strong> (<a href="https://huggingface.co/sdadas/polish-gpt2-xl/tree/main">polish-gpt2</a>) and <strong>Dariusz Kleczek</strong> (<a href="https://huggingface.co/dkleczek/papuGaPT2">papuGaPT2</a>).</p>
<h1>3. Data Collection</h1>
<p>Selected sources:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/c060382e-5c79-4736-ba67-d772cd05d878.png" alt="003_logos_all" style="display:block;margin:0 auto" />

<p>To train <strong>Toktumi 1.0</strong>, I used <strong>3 115 002 text samples</strong> that I had collected from the following sources: (<strong>Wykop, Wikipedia, Fakt, Pudelek, Polsat News, books, and jokes</strong>).</p>
<p>My goal was to gather texts representing <strong>natural, colloquial language</strong> and build a model capable of <strong>holding conversations</strong> on a wide range of topics in an <strong>informal, conversational style</strong>.</p>
<p>At this stage, I did not yet know which types of text would work best in the training dataset. My choice of materials was therefore partly experimental — I made many decisions <strong>through trial and error</strong>, gradually learning about the model's capabilities and possible directions for further development.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/6e8a2532-b0f0-44ce-a1c9-dbc129748e4c.png" alt="006_wykres_teksty" style="display:block;margin:0 auto" />

<p>a) <a href="https://pl.wikipedia.org"><strong>Wikipedia</strong></a> - I used a data dump of the Polish-language edition of Wikipedia, made available by the Wikimedia Foundation <a href="https://dumps.wikimedia.org/plwiki/latest/">for download</a> under the Creative Commons CC BY-SA 4.0 license. I then converted the XML file into plain text.</p>
<p>I extracted <strong>1 485 976 entries</strong>, of which I ultimately used approximately half <strong>(718 129)</strong>, so that Wikipedia's style would not 'dominate' the model's responses.</p>
<p>Wikipedia texts are encyclopedic entries — rich in information, focused on facts, and written in neutral language without an emotional tone or personal opinions.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/619920c7-866c-4342-a816-5a161b88a53a.png" alt="007_wikitext" style="display:block;margin:0 auto" />

<p>b) <a href="https://www.fakt.pl/"><strong>Fakt.pl</strong></a> - To collect texts, I used various <strong>web scraping</strong> methods for automatically downloading and processing website content. I wrote scripts in <strong>Python</strong>, using the <strong>Scrapy</strong> library and the <strong>Scrapy-Splash</strong> extension running in a <strong>Docker</strong> container.</p>
<p>The scripts automatically downloaded articles, extracted their content from HTML, and saved each text in a separate TXT file.</p>
<p>In this way, I collected <strong>573 445 text samples</strong>, ready for further processing and model training.</p>
<p>Fakt publishes texts on a wide range of topics, including news reports and opinion pieces as well as <strong>more tabloid-style content</strong>. Its articles are often sensational or emotional and may express opinions. The site offers, among other things, articles, reviews, advice pieces, and horoscopes — <strong>a broad range of topics</strong>. Texts of this kind help the model develop a <strong>more colloquial style</strong> and expand its knowledge of the world across different fields.</p>
<p>Later tests and experience showed that this was <strong>one of the most valuable sources of text</strong> in the entire project, primarily because of its accessible language, variety of genres, and very wide range of topics.</p>
<p>How Scrapy works:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/6182692a-53fa-41a2-87db-37962cd19967.png" alt="008_web_scraping" style="display:block;margin:0 auto" />

<p>c) <a href="https://www.polsatnews.pl/"><strong>PolsatNews.pl</strong></a> - I used the same method (<strong>Scrapy, Splash, Docker</strong>) to collect texts from this news website.</p>
<p>The challenge was finding a list of article URLs to download, since the website did not provide one. To solve this, I wrote a <strong>crawler</strong> in Python using the <strong>Requests</strong> and <strong>BeautifulSoup</strong> libraries. It automatically found links to articles, which then served as the basis for scraping their content with Scrapy and Splash. In this way, I collected <strong>126 004 text samples</strong>.</p>
<p>Polsat News publishes news reports <strong>on current events in Poland and abroad</strong>. Its news style is formal, factual, and straightforward. The articles contain information from official, verified sources. This type of text helps the model expand its <strong>knowledge of the world</strong> and learn to use neutral language, but in a style different from Wikipedia's.</p>
<p>d) <a href="https://www.pudelek.pl/"><strong>Pudelek.pl</strong></a> - I used the same web scraping method (<strong>Scrapy, Splash, Docker</strong>) to collect texts. In this way, I gathered <strong>165 229 text samples</strong>.</p>
<p>Pudelek features <strong>gossip-oriented content</strong> — short articles about celebrities, stars, and show business. They are written in a distinctive style full of irony and snark, often playing on readers' emotions. Texts of this kind help the model imitate lively, informal language, and the typical 'Pudelek style' is immediately recognizable.</p>
<p>Pudelek also publishes distinctive, often biting <strong>user comments</strong>. These comments were downloaded along with the articles. Ultimately, however, I did not use them, because they appear on the website in a random order. As a result, when the model learned from them, it could not maintain a logical connection between the prompt and the response.</p>
<p>e) <a href="https://wykop.pl/"><strong>Wykop</strong></a> - Wykop is a popular website where users share online "finds," publish posts, and comment on and rate content posted by others. For training the model, I considered it a valuable source of <strong>internet-style user comments</strong>.</p>
<p>To collect the texts, I used <strong>Python</strong> and the <strong>Selenium</strong> library with <strong>GeckoDriver</strong>, which makes it possible to automate the Firefox browser. In this way, I collected <strong>1 413 999 text samples</strong>, including posts and user comments.</p>
<p>Ultimately, however, it turned out that <strong>using internet comments, like those from Pudelek, was not a good choice</strong>. The comments often contained chaotic, offensive, or off-topic statements that negatively affected the quality of the model's responses. That is why, in later versions of Toktumi, I <strong>completely abandoned this data source</strong>.</p>
<p>How web scraping with Selenium works:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/03e6e65e-85ff-400b-ba02-f6f348ce1198.png" alt="009_Selenium" style="display:block;margin:0 auto" />

<p>f) <strong>Jokes</strong> - I also decided to add jokes collected from more than a dozen publicly accessible websites to the training dataset. This was an experiment to see whether the model could learn to generate coherent humorous texts.</p>
<p>It turned out, however, that teaching a model humor is much harder than I initially expected. It requires an understanding of irony, wordplay, and various linguistic subtleties. Unexpectedly, though, <strong>jokes helped the model learn the question–answer (QA) structure</strong> — the principle that a question should be followed by an answer. This structure often appears in jokes, whereas the other texts in the training dataset generally did not contain it.</p>
<p>After removing duplicates, I collected <strong>88 897 jokes</strong>.</p>
<p>The dataset also required <strong>additional cleaning and filtering of offensive or inappropriate content</strong>. Jokes are often posted directly by internet users, without adequate moderation. Attempts to remove such content did not always produce satisfactory results. This showed how difficult it is to prepare a valuable training dataset from texts obtained from open online sources.</p>
<p>To download the texts, I used automated browser control (<strong>Firefox, GeckoDriver, Selenium, and Python</strong>).</p>
<p>g) <strong>Books</strong> - Finally, I added texts from <strong>285 books</strong> found online, divided into <strong>29 299 fragments</strong>. These included <strong>user manuals, guides, popular science books, reportage, and cookbooks</strong>, among others. The aim was to provide additional, more in-depth content.</p>
<p>I found that fiction and imaginative literature in the broad sense were unsuitable for training: plays, poetry, and even movie subtitles. As the tests showed, the model would then start producing language that was overly abstract, taken out of context, or difficult to understand, or it would adopt a novel-like style that made casual conversation difficult. Therefore, when selecting texts in this category, I limited myself to non-fiction and works discussing the real world.</p>
<p>The book texts had to be split into smaller fragments while preserving <strong>the broader context</strong>. For this purpose, I used an <strong>overlapping sliding window</strong> method, in which the end of one fragment is repeated at the beginning of the next. This helped maintain continuity and reduce the loss of context.</p>
<p>Splitting data using the overlapping method:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/36e3f2d5-1835-4be2-97e1-c59cd172ec9f.png" alt="010_overlapping" style="display:block;margin:0 auto" />

<h1>4. Creating the Training File (training set)</h1>
<p>I saved each text <strong>in a separate .txt file</strong>. I chose this format because of its simplicity — text files are easy to browse, edit, and clean, without requiring an additional data structure such as JSON. This made the dataset easier to manage. For example, I could sort the files <strong>by size</strong> and remove the smallest ones — those that were empty or not useful for training the model.</p>
<p>I split the largest .txt files, exceeding <strong>1024 tokens</strong>, into smaller fragments using the overlapping method.</p>
<p>Conversely, I combined shorter texts of the same type (such as jokes or short articles) into larger 'batches' of approximately 1024 tokens, to keep the training dataset well balanced. Each individual text within a batch was separated by a delimiter.</p>
<p>Combining texts of the same type into larger batches:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/cde712c8-4536-4ead-a639-20c57e6e1ecb.png" alt="011_łączenie" style="display:block;margin:0 auto" />

<p>Each dataset also had to be <strong>cleaned</strong>. This was a very time-consuming process involving many stages, which I will not describe in detail here. However, it is worth emphasizing that cleaning and preparing the data <strong>takes the most time — more than collecting the data and training the model itself</strong>.</p>
<p>This shows just how <strong>much work this stage requires</strong>. At first it may seem less important, but in reality it is one of the most time-consuming and crucial parts of the entire model-training process. It is worth remembering the principle of <strong>"Garbage In, Garbage Out"</strong> — the quality of the input data has a fundamental impact on the quality of the results.</p>
<p>The next step was <strong>reducing the set of characters used</strong> in the collected texts. To do this, I converted all texts from <strong>UTF-8</strong> encoding to <strong>Windows-1250</strong> (up to 256 characters) and back again (to maintain compatibility with the tools used during training). During this conversion and additional cleaning, characters outside that set were replaced or removed.</p>
<p>As a result, the GPT-2 model did not have to process many unusual characters that added little linguistic value. Limiting the number of characters was also intended to <strong>improve tokenizer efficiency</strong> (in my case, <strong>the vocabulary size was 50 000 tokens</strong>).</p>
<p>Reducing the character set during conversion from UTF-8 to ANSI:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/722e19db-0c3b-4c99-acce-c0e9baace3bf.png" alt="012_ansi" style="display:block;margin:0 auto" />

<p>Next, I combined all the .txt files containing the collected texts into <strong>one large training dataset (training set)</strong>, which was then used to train the model. Each of the <strong>more than 3 million</strong> text samples was separated by the <code>&lt;|endoftext|&gt;</code> token — the same delimiter used by OpenAI in the original GPT-2 model.</p>
<p>I also set aside a smaller <strong>validation set</strong> containing data in the same format that <strong>did not overlap</strong> with the training set. I later used it to evaluate the model's quality (by measuring <strong>perplexity</strong>).</p>
<p>How the texts were divided into training and validation sets:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/29bae01f-ac02-4673-b7ac-ab6488499e11.png" alt="013_train_dataset" style="display:block;margin:0 auto" />

<h3>TOKTUMI 1.0 TRAINING DATASET SIZE (excluding delimiters):</h3>
<blockquote>
<p>Number of text samples: <strong>3 115 002</strong></p>
<p>Number of words: <strong>658 078 092</strong></p>
<p>Number of characters: <strong>4 480 020 887</strong></p>
<p>Number of tokens: <strong>978 054 512</strong></p>
<p>File size: <strong>4.46 GB</strong></p>
</blockquote>
<h1>5. Preparing the Tokenizer</h1>
<p><strong>A tokenizer</strong> is a set of files and rules that helps a model convert text into tokens — smaller units into which text is divided. The model operates on these tokens, converting them <strong>into numerical values</strong> and ultimately back into text. Proper tokenizer preparation is essential for data consistency, the quality of generated responses, and the model's overall performance. A poorly prepared tokenizer can significantly harm training and the quality of generated text.</p>
<p>Instead of using a ready-made tokenizer, I decided to build my own, tailored to the Polish language, using a large sample of the texts I had previously collected.</p>
<p>Files commonly included with a tokenizer: <strong>vocab.json, merges.txt, tokenizer_config.json, special_tokens_map.json, tokenizer.json</strong>.</p>
<p>Contents of the vocab.json file:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0408c30e-57a5-4564-b232-70a925f2d4fc.png" alt="014_tokenizer" style="display:block;margin:0 auto" />

<p>Below is the key section of the <strong>code used to create the tokenizer</strong>.</p>
<p>I chose a vocabulary size of <strong>vocab_size=50_000</strong>, close to that of the original GPT-2 tokenizer, which contained 50 257 tokens across all model variants.</p>
<p>I used the <strong>Tokenizers</strong> and <strong>Transformers</strong> libraries to create the tokenizer.</p>
<pre><code class="language-python">from tokenizers import ByteLevelBPETokenizer
from transformers import GPT2TokenizerFast

# Utworzenie i trening własnego tokenizera BPE
tokenizer = ByteLevelBPETokenizer()

tokenizer.train(
    files="tokenizer_teksty.txt",
    vocab_size=50_000,
    min_frequency=2,
    special_tokens=[
        "&lt;|endoftext|&gt;",
        "&lt;unk&gt;",
        "[PAD]"
    ]
)

# Zapis słownika i reguł BPE
tokenizer_path = "tokenizer_2025_endoftext"
tokenizer.save_model(tokenizer_path)

# Przygotowanie tokenizera do pracy z Transformers
fast_tokenizer = GPT2TokenizerFast(
    vocab_file=f"{tokenizer_path}/vocab.json",
    merges_file=f"{tokenizer_path}/merges.txt",
    unk_token="&lt;unk&gt;",
    pad_token="[PAD]",
    eos_token="&lt;|endoftext|&gt;"
)

fast_tokenizer.save_pretrained(tokenizer_path)
</code></pre>
<p>After creating the tokenizer, I needed to verify that it worked correctly. I tested text encoding and decoding and checked that the special token <code>&lt;|endoftext|&gt;</code> was correctly recognized as a delimiter. All tests passed successfully.</p>
<p>The tokenization process and its verification:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/794bd289-299b-4be9-872f-8af7b57dfe37.gif" alt="018_tokenizer" style="display:block;margin:0 auto" />

<h1>6. Creating an Empty GPT-2 Medium Model</h1>
<p>Next, I created an <strong>empty GPT-2 model</strong> with no pretrained weights. The model configuration (number of layers and attention heads, embedding dimension, context length, and vocabulary size) was matched to the <strong>GPT-2 Medium variant</strong> and the tokenizer I had prepared. This was the starting point for training GPT-2 from scratch.</p>
<pre><code class="language-python">from transformers import GPT2LMHeadModel, GPT2Config

# Konfiguracja architektury GPT-2 Medium
config_medium = GPT2Config(
    vocab_size=50_000,  # Rozmiar słownika
    n_positions=1024,   # Maksymalna długość sekwencji
    n_ctx=1024,         # Długość kontekstu
    n_embd=1024,        # Wymiar osadzeń
    n_layer=24,         # Liczba warstw
    n_head=16,          # Liczba głowic atencji
    bos_token_id=0,
    eos_token_id=0,
    pad_token_id=2
)

# Inicjalizacja modelu z losowymi wagami
model_medium = GPT2LMHeadModel(config_medium)

# Zapis modelu i jego konfiguracji
model_medium.save_pretrained("GPT2-MEDIUM-empty_new")
</code></pre>
<h1>7. Training the Model</h1>
<p>I trained the model locally on an <strong>NVIDIA GeForce RTX 3060 (12 GB VRAM)</strong>, using <strong>Python, PyTorch, Transformers, and CUDA</strong>. One of the first challenges was finding compatible versions of the libraries and drivers — incompatibilities often caused errors or prevented GPU acceleration from working.</p>
<p>After setting up the environment, it is important to check whether PyTorch correctly detects CUDA:</p>
<pre><code class="language-python">import torch

print("Czy CUDA dostępne =", torch.cuda.is_available())
</code></pre>
<p><strong>Using a GPU significantly speeds up model training</strong> compared with running computations on the CPU alone.</p>
<h3>TRAINING — KEY CHALLENGES:</h3>
<p>Below is an excerpt from the <strong>training</strong> log. The values on the left show the number of completed <strong>steps</strong> and the total number of steps planned for the entire run (<strong>1140000/1771840</strong>). This means that <strong>1 140 000</strong> out of <strong>1 771 840</strong> planned steps had been completed. On the right, the value 'epoch': 6.43 indicates that the model is in its <strong>sixth epoch</strong>. The entire training run was planned for <strong>10 epochs</strong>, so overall progress at this point was approximately <strong>64%</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/81063391-741a-4241-ac79-3c9e03bb2196.png" alt="015_epoki_Toktumi1" style="display:block;margin:0 auto" />

<p>Key concepts for monitoring training progress:</p>
<ul>
<li><strong>Steps</strong> indicate how many times the model's weights have been updated. A step involves processing one or more batches of data, calculating the loss, and updating the model parameters.</li>
<li><strong>Epochs</strong> indicate how many times the entire training dataset has been processed. One epoch is one complete pass through the training data. Multiple epochs are usually needed for the model to achieve good prediction quality.</li>
</ul>
<p>Before I developed the final model-training code, I had to conduct many rounds of tests on smaller datasets (text files of approximately 50-100 MB). One of the more serious challenges was deciding: <strong>after how many epochs should training stop?</strong></p>
<p>Initially, I tested code with <strong>early stopping</strong> and two datasets: a training set and a validation set. Training stopped automatically <strong>at around the seventh epoch</strong>. Based on this, I adopted a safe upper limit of 10 epochs for the final training code. Ultimately, I abandoned early stopping because it proved impractical for training on a large dataset over multiple sessions, when the process had to be broken into smaller stages (and repeatedly paused and resumed).</p>
<p>To determine the best time to stop training, measuring <strong>perplexity</strong> proved more practical, as I explain in greater detail later.</p>
<p>Below is the most important part of the <strong>code used to train Toktumi 1.0</strong>, including the <strong>hyperparameter configuration</strong>.</p>
<pre><code class="language-python">from transformers import (
    GPT2LMHeadModel, GPT2Tokenizer, TextDataset,
    DataCollatorForLanguageModeling, Trainer, TrainingArguments
)

# Model, tokenizer i dane treningowe
model_path = "GPT2-MEDIUM-empty_new"
tokenizer_path = "tokenizer_2025_endoftext"
train_path = "BIG_UCZ_fin_v2.txt"
output_dir = "GPT2-MEDIUM-Toktumi-1"

model = GPT2LMHeadModel.from_pretrained(model_path)
tokenizer = GPT2Tokenizer.from_pretrained(tokenizer_path)

# Dopasowanie tokenizera do modelu
tokenizer.add_special_tokens({"eos_token": "&lt;|endoftext|&gt;"})
model.resize_token_embeddings(len(tokenizer))

# Przygotowanie danych treningowych
train_dataset = TextDataset(
    tokenizer=tokenizer,
    file_path=train_path,
    block_size=230  # Długość sekwencji dopasowana do pamięci GPU
)

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False  # Uczenie przewidywania kolejnego tokenu
)

# Hiperparametry treningu
training_args = TrainingArguments(
    output_dir=output_dir,
    overwrite_output_dir=True,
    num_train_epochs=10,             # Maksymalna liczba epok
    per_device_train_batch_size=8,   # Wielkość batcha
    gradient_accumulation_steps=3,   # Akumulacja gradientów
    learning_rate=5e-5,              # Współczynnik uczenia
    fp16=True,                       # Mieszana precyzja (oszczędność VRAM)
    save_steps=7500,                 # Zapis checkpointu co 7500 kroków
    save_total_limit=300,            # Limit zapisanych checkpointów
    logging_steps=500,               # Logowanie postępu
    save_safetensors=False
)

# Inicjalizacja treningu
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator
)

# Trening od zera
trainer.train()

# Alternatywnie: wznowienie z ostatniego checkpointu
# trainer.train(resume_from_checkpoint=True)

# Zapis wytrenowanego modelu i tokenizera
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)
</code></pre>
<p>During training, the <strong>automatic checkpoint-saving feature</strong> proved extremely useful (in this case, <strong>'save_steps=7500'</strong>, meaning a checkpoint was saved every 7500 steps).</p>
<p>Directory containing the checkpoints:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/7888cab7-4086-4eba-b075-638fe2b8bf27.png" alt="016_checkpointy" style="display:block;margin:0 auto" />

<p>Each checkpoint directory contains the complete model state as well as the necessary temporary files. Checkpoints make it possible to <strong>resume training from exactly the point at which a checkpoint was saved</strong>. However, storing many checkpoints requires a considerable amount of disk space.</p>
<p>This is particularly important for long training runs lasting many days, when power outages, the need to restart the computer, or other unforeseen events may occur.</p>
<h3>END OF TRAINING</h3>
<p>Ultimately, the most suitable point at which to stop training turned out to be <strong>checkpoint-1140000</strong>, corresponding to approximately <strong>6.44 epochs</strong>.</p>
<p>Training time: <strong>approximately 380 hours (over 15 days)</strong>.</p>
<h1>8. Evaluating Model Quality. Perplexity.</h1>
<p>To assess the quality of the trained GPT-2 Medium model, I used <strong>perplexity</strong>. Perplexity measures how well a model predicts the next words in a sequence — <strong>the lower the perplexity, the better the model fits the data</strong>.</p>
<p>The model was evaluated using the previously separated <strong>validation set</strong> — data the model had not 'seen' before. The result is calculated using the trainer.evaluate() function, which returns the loss value.</p>
<pre><code class="language-python">perplexity = math.exp(eval_results["eval_loss"])
</code></pre>
<p>Because of the technical complexity of this task, I relied on <a href="https://www.comet.com/site/blog/perplexity-for-llm-evaluation/">existing solutions</a>.</p>
<p>The approach works as follows: the code iterates <strong>through the saved checkpoints</strong> and calculates <strong>perplexity</strong> for selected ones. This makes it possible to estimate when <strong>further training is no longer worth the cost</strong>.</p>
<p>As the chart below shows, at a certain point the decline in perplexity slowed considerably, and further training would have required substantial computing resources for very little improvement. Based on this, I decided to stop training early, before reaching the planned 10 epochs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fc1350cc-e1fa-4aeb-98be-fd51c907b8da.png" alt="17_perplexity_Toktumi1" style="display:block;margin:0 auto" />

<h1>9. Evaluating Model Quality. Prompting.</h1>
<p>The second stage of model evaluation involved <strong>human evaluation</strong> — checking how Toktumi responds to specific prompts.</p>
<p>For these tests, I used <strong>greedy decoding</strong> — a method in which the model selects the most probable next token. As a result, the same prompt always produced the same response, providing a consistent reference point for comparing checkpoints.</p>
<p>This can be achieved by setting <code>do_sample=False</code> or, as in my tests, <code>do_sample=True</code> <strong>and</strong> <code>top_k=1</code>. The remaining parameters control response length and help prevent repeated passages.</p>
<p>Below is the key section of the code I used to generate and test Toktumi 1.0 responses.</p>
<pre><code class="language-python">import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

# Wczytanie modelu Toktumi 1.0 i tokenizera
model_path = "GPT2-MEDIUM-Toktumi-1"
model = GPT2LMHeadModel.from_pretrained(model_path)
tokenizer = GPT2Tokenizer.from_pretrained(model_path)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device).eval()

# Przykładowy prompt
prompt = "Co to jest grawitacja?"
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)

# Parametry generowania odpowiedzi
generate_args = {
    "max_length": 256,            # Maksymalna długość: prompt + odpowiedź
    "min_new_tokens": 2,          # Minimum 2 nowe tokeny
    "no_repeat_ngram_size": 2,    # Blokowanie powtarzających się par tokenów
    "repetition_penalty": 1.1,    # Kara za powtarzanie tokenów
    "do_sample": True,            # Włączony tryb samplingu
    "top_k": 1,                   # Wybór tylko najbardziej prawdopodobnego tokenu
    "pad_token_id": tokenizer.eos_token_id,
    "eos_token_id": tokenizer.eos_token_id
}

# Generowanie i dekodowanie odpowiedzi
with torch.no_grad():
    output = model.generate(input_ids, **generate_args)

generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)
</code></pre>
<p>The model produces the most probable response.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest grawitacja?</em> [EN: <em>What is gravity?</em>]</p>
<p><strong>Answer:</strong> <em>- To jest siła, która przyciąga przedmioty.</em> [EN: <em>- It is a force that attracts objects.</em>]</p>
</blockquote>
<p>To identify the <strong>best checkpoint</strong> in terms of response quality, I tested selected checkpoints on a set of <strong>150 prompts</strong> with different structures: <strong>sentence completions, questions, and instructions</strong>:</p>
<ul>
<li><strong>Sentence completions</strong>, e.g. "Warszawa jest stolicą..." ("Warsaw is the capital of...") and "Ziemia krąży wokół..." ("Earth orbits..."). The model generally recognized this as a structure that required completing a sentence. It also often provided a correct or the most "intuitive" completion. This category produced the fewest errors.</li>
<li><strong>Questions</strong>, ending with a question mark, e.g. "Co to jest planeta?" ("What is a planet?") and "Do czego służy ołówek?" ("What is a pencil used for?"). The model was expected to answer a question, just as a person would. It often recognized the question–answer pattern correctly. However, it also frequently struggled both with understanding the structure (that a question calls for an answer) and with factual knowledge about the world (it responded, but incorrectly).</li>
<li><strong>Instructions</strong>, e.g. "Wymień trzy warzywa" ("Name three vegetables") and "Podaj przykład pojazdu" ("Give an example of a vehicle"). An instruction is not a question, but it still requires an appropriate reaction — in this case, following the instruction or providing an answer. This type of prompt was the most difficult for the model, because it often failed to recognize that a response was required.</li>
</ul>
<p>The full list of 150 prompts and Toktumi 1.0 responses (using 'greedy decoding') -&gt; <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_1.0_150_prompts_checkpoint-1140000.txt"><strong>VIEW</strong></a>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/870b69c3-5e1e-4c1b-970d-72a89a242abe.png" alt="020_prompty" style="display:block;margin:0 auto" />

<h1>10. Ending the Training Run</h1>
<p>After analyzing <strong>perplexity</strong> and the results of the <strong>150-prompt tests</strong>, I selected <strong>checkpoint-1140000</strong>, saved after approximately <strong>6,44 epochs</strong>, as the best checkpoint.</p>
<p>Further training was no longer producing enough improvement in response quality, so I decided to end the training run at this point.</p>
<p>The selected checkpoint contains the saved model weights. It forms the basis of the final <strong>Toktumi 1.0</strong> model.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a9e0b84a-95ab-4893-a73c-49f1f15e6673.png" alt="021_model_ready" style="display:block;margin:0 auto" />

<h1>11. Final Model Evaluation</h1>
<p>The final step was to <strong>evaluate the model</strong> and determine to what extent it met the objectives established at the beginning of the project — namely, <strong>whether it could hold conversations on a wide range of topics using everyday, conversational language</strong>.</p>
<p>Here, I will focus on the most important observations. The examples below were generated using <strong>greedy decoding</strong>.</p>
<h3>a) Recognizing the Prompt</h3>
<p><strong>FINDINGS:</strong> The model often recognizes the structure of a prompt and generates a response that is <strong>grammatically and syntactically correct</strong>. It also uses an <strong>appropriate style</strong>. Interestingly, the model learned the <strong>Question–Answer</strong> structure mainly from the collection of jokes — many jokes take the form of a question followed by an answer (the punchline). This helped the model learn a simple rule: <strong>when a question is asked, it should be answered</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest grawitacja?</em> [EN: <em>What is gravity?</em>]</p>
<p><strong>Answer:</strong> <em>- To jest siła, która przyciąga przedmioty.</em> [EN: <em>- It is a force that attracts objects.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co robi kucharz?</em> [EN: <em>What does a cook do?</em>]</p>
<p><strong>Answer:</strong> <em>- Smaży kotlety.</em> [EN: <em>- Fries cutlets.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Warzywa są zdrowe, bo</em> [EN: <em>Vegetables are healthy because</em>]</p>
<p><strong>Answer:</strong> <em>Warzywa są zdrowe, bo zawierają dużo witamin i minerałów.</em> [EN: <em>Vegetables are healthy because they contain plenty of vitamins and minerals.</em>]</p>
</blockquote>
<h3>b) Basic Knowledge of the World</h3>
<p><strong>When a prompt is relatively simple</strong>, concerns commonly known facts, and does not require complex reasoning, <strong>the model often gives a correct answer</strong>. It therefore has <strong>general knowledge of the world</strong> and knows <strong>basic facts</strong>.</p>
<p>It performs best on prompts that require <strong>completing a sentence</strong> — this is the easiest format for the model to decode.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Stolicą Francji jest</em> [EN: <em>The capital of France is</em>]</p>
<p><strong>Answer:</strong> <em>- Stolicą Francji jest Paryż.</em> [EN: <em>- The capital of France is Paris.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Najwyższą górą na świecie jest</em> [EN: <em>The highest mountain in the world is</em>]</p>
<p><strong>Answer:</strong> <em>Najwyższą górą na świecie jest Mount Everest, który ma wysokość 720 metrów.</em> [EN: <em>The highest mountain in the world is Mount Everest, which is 720 meters tall.</em>]</p>
</blockquote>
<p>(although the model gets the height wrong)</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Najdłuższą rzeką w Polsce jest</em> [EN: <em>The longest river in Poland is</em>]</p>
<p><strong>Answer:</strong> <em>Najdłuższą rzeką w Polsce jest rzeka Wisła.</em> [EN: <em>The longest river in Poland is the Vistula River.</em>]</p>
</blockquote>
<h3>c) Incorrect Answers</h3>
<p>Sometimes the model generates a response that has the correct structure but is factually wrong — giving the impression that it is "making things up." This phenomenon is known as <strong>hallucination</strong>. It occurs when a model presents false or invented information as though it were factual.</p>
<p>As the tests show, hallucinations occur more frequently in <strong>less well-trained models</strong>, especially during the early stages of training. This is somewhat like <strong>a person who does not know the answer but tries to guess it anyway</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Do czego służy ołówek?</em> [EN: <em>What is a pencil used for?</em>]</p>
<p><strong>Answer:</strong> <em>- Do czyszczenia akwariów.</em> [EN: <em>- For cleaning aquariums.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Z czego robi się mąkę?</em> [EN: <em>What is flour made from?</em>]</p>
<p><strong>Answer:</strong> <em>- Z proszku do pieczenia.</em> [EN: <em>- From baking powder.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Najszybszym zwierzęciem na lądzie jest</em> [EN: <em>The fastest land animal is</em>]</p>
<p><strong>Answer:</strong> <em>Najszybszym zwierzęciem na lądzie jest koń.</em> [EN: <em>The fastest land animal is a horse.</em>]</p>
</blockquote>
<h3>d) Lack of Understanding</h3>
<p>Sometimes the model misinterprets the structure of a question or provides an internally contradictory answer.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Księżyc świeci w</em> [EN: <em>The Moon shines at</em>]</p>
<p><strong>Answer:</strong> <em>Księżyc świeci w nocy, a księżyc świeci nocą.</em> [EN: <em>The Moon shines at night, and the Moon shines during the night.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest ogień?</em> [EN: <em>What is fire?</em>]</p>
<p><strong>Answer:</strong> <em>- To jest taki mały, biały, czerwony, świecący, czarny, żółty, zielony, niebieski, pomarańczowy, brązowy, fioletowy.</em> [EN: <em>- It's this little, white, red, glowing, black, yellow, green, blue, orange, brown, purple thing.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie jedzenie przygotowujesz, kiedy masz gości?</em> [EN: <em>What food do you prepare when you have guests?</em>]</p>
<p><strong>Answer:</strong> <em>- Na początku tygodnia.</em> [EN: <em>- At the beginning of the week.</em>]</p>
</blockquote>
<p>Additionally, when a prompt contains <strong>instructions</strong>, the model very often <strong>fails to recognize the expected type of response at all</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Podziel się swoimi planami na weekend.</em> [EN: <em>Share your plans for the weekend.</em>]</p>
<p><strong>Answer:</strong> <em>Horoskop dzienny - Lew. Dzisiejszy dzień przyniesie Ci wiele możliwości do rozwoju osobistego i zawodowego...</em> [EN: <em>Daily horoscope - Leo. Today will bring you many opportunities for personal and professional growth...</em>]</p>
</blockquote>
<p>(unexpectedly responds in the style of a horoscope)</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wymień dwie przyprawy.</em> [EN: <em>Name two spices.</em>]</p>
<p><strong>Answer:</strong> <em>Wymień dwie przyprawy. To proste!</em> [EN: <em>Name two spices. It's easy!</em>]</p>
</blockquote>
<p>(quotes a passage from an article on Pudelek or Fakt)</p>
<h3>e) The 'Komentarz:' ('Comment:') Section</h3>
<p>As I mentioned earlier, texts from the '<a href="http://Wykop.pl">Wykop.pl</a>' website were included in the Toktumi 1.0 training dataset together with user comments. This was the largest category of texts in the training dataset (<strong>1 413 999 samples</strong>).</p>
<p>The tests showed that internet comments significantly reduced the quality of the generated responses. They were rarely relevant to the topic and, despite moderation, still contained <strong>judgmental and offensive language, reinforced harmful stereotypes, and even included conspiracy theories</strong>.</p>
<p>Rather than simply misinterpreting the instruction, the model <strong>imitates the style of internet comments</strong> — often chaotic, judgmental, and inappropriate for the role of a friendly assistant.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie znasz środki transportu?</em> [EN: <em>What means of transportation do you know?</em>]</p>
<p><strong>Answer:</strong> <em>(...)</em></p>
<p><strong>Comment:</strong> <em>Nie znam, ale się wypowiem.</em> [EN: <em>I don't know, but I'll give my opinion anyway.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co sądzisz o podróżach po Europie?</em> [EN: <em>What do you think about traveling around Europe?</em>]</p>
<p><strong>Answer:</strong> <em>(...)</em></p>
<p><strong>Comment:</strong> <em>Nie wiem, czy to dobry pomysł. W Polsce jest dużo taniej niż w Niemczech.</em> [EN: <em>I don't know if that's a good idea. It's much cheaper in Poland than in Germany.</em>]</p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Ziemia krąży wokół</em> [EN: <em>The Earth revolves around</em>]</p>
<p><strong>Answer:</strong> <em>Ziemia krąży wokół Słońca.</em> [EN: <em>The Earth revolves around the Sun.</em>]</p>
<p><strong>Comment:</strong> <em>To jest niesamowite, że w XXI wieku ludzie nadal wierzą w takie bzdury. (...) To tak jakby wierzyć, iż Ziemia krąży dookoła Słońca i jest okrągła. Albo, żeby wierzyć w płaską ziemię. No ale przecież ziemia jest płaska, więc ...</em> [EN: <em>It's incredible that in the 21st century people still believe such nonsense. (...) It's like believing that the Earth revolves around the Sun and is round. Or believing in a flat Earth. But of course the Earth is flat, so ...</em>]</p>
</blockquote>
<h1>12. Recommendations and Next Steps</h1>
<p>Based on these observations, I developed a short set of recommendations for further improving the model. <strong>I implemented the first two while developing the next version — Toktumi 2.0.</strong></p>
<h3>RECOMMENDATIONS:</h3>
<ol>
<li><strong>Remove internet comments</strong> from the training dataset.</li>
<li>Conduct <strong>fine-tuning</strong> of the model (<strong>or train it again</strong> from scratch) on <strong>question–answer (QA)</strong> and <strong>instruction–answer (instruction fine-tuning)</strong> datasets.</li>
<li>Apply <strong>advanced text-cleaning techniques</strong>, with more effective <strong>filtering of undesirable and harmful content</strong>.</li>
<li>Consider using a GPT-2 model <strong>with more parameters</strong>: <strong>GPT-2 Large (762M)</strong> or <strong>GPT-2 XL (1.5B)</strong>. GPT-2 Medium may have too little capacity and a limited attention mechanism to effectively retain knowledge and generate more complex responses. However, hardware constraints may be a barrier.</li>
<li>Use a <strong>newer model architecture</strong> that offers greater capabilities than GPT-2. However, hardware limitations and the high cost of training more advanced models may be obstacles.</li>
</ol>
<h2>Toktumi 1.0 on Hugging Face Spaces</h2>
<p>You can try <strong>Toktumi 1.0</strong> on <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-1.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-1.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fe99cff4-72fc-415d-87e0-f348ef2b9bc6.png" alt="51_T1_interface" style="display:block;margin:0 auto" />

<p><a href="https://sixfingers.hashnode.dev/toktumi-2-en"><strong>NEXT PART</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Jak zbudować duży model językowy od zera? Toktumi 3.0 – Część 3 (i ostatnia)]]></title><description><![CDATA[Toktumi 3.0: fine-tuning GPT-2 na 6,19 mln syntetycznych rekordów QA, IA i dialogów. Early stopping, 29 checkpointów i ocena jakości.]]></description><link>https://sixfingers.hashnode.dev/toktumi-3-pl</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-3-pl</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[nlp]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[fine tuning]]></category><category><![CDATA[synthetic data]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 21:09:12 GMT</pubDate><content:encoded><![CDATA[<p>🇬🇧 <strong><a href="https://sixfingers.hashnode.dev/toktumi-3-en">ENGLISH VERSION</a></strong></p>
<h1><strong>ROZDZIAŁ III. Toktumi 3.0. Trzeci trening.</strong></h1>
<h3>1. Cel ponownego treningu</h3>
<p>Toktumi 2.0 był znacznym postępem w stosunku do Toktumi 1.0, ale nadal miał problemy z poprawnością odpowiedzi, rozumieniem niektórych pojęć i zapętlaniem tekstu. (Szczegóły – zobacz <a href="https://sixfingers.hashnode.dev/toktumi-2-pl"><strong>Część 2</strong></a>).</p>
<p>Wcześniejsze eksperymenty pokazały, że <strong>uczenie na parach pytanie–odpowiedź (QA) oraz polecenie–odpowiedź (IA)</strong> przynosi dobre rezultaty. Postanowiłem więc rozwinąć to podejście, wykorzystując wcześniej zgromadzone teksty oraz API Google Gemini (<strong>Gemini 3.1 Flash-Lite</strong>) do syntetycznego generowania danych treningowych.</p>
<p><strong>Chciałem sprawdzić, czy dalsze uczenie modelu wyłącznie na krótkich, uporządkowanych pytaniach, instrukcjach i dialogach pozwoli uzyskać lepsze rezultaty niż trening oparty głównie na surowych tekstach.</strong></p>
<p>Podobnie jak poprzednie etapy, <strong>projekt ma charakter niekomercyjny i edukacyjno-badawczy.</strong></p>
<h3>2. Nowa koncepcja danych treningowych</h3>
<p>W poprzednich wersjach Toktumi znaczną część korpusu stanowiły surowe teksty pozyskane z internetu: artykuły, książki, komentarze internetowe i inne materiały. W Toktumi 3.0 postanowiłem całkowicie zmienić to podejście.</p>
<p><strong>Zamiast uczyć model bezpośrednio na surowych tekstach, cały korpus treningowy został przygotowany w formie krótkich, uporządkowanych rekordów</strong>, generowanych na podstawie materiałów źródłowych.</p>
<p>Przyjęto trzy podstawowe rodzaje rekordów:</p>
<ul>
<li><strong>Pytanie → odpowiedź (QA)</strong> — rozwijanie wiedzy i rozumienia pojęć.</li>
<li><strong>Polecenie → odpowiedź (IA)</strong> — uczenie wykonywania instrukcji.</li>
<li><strong>Wypowiedź → reakcja (dialog)</strong> — rozwijanie umiejętności prowadzenia rozmowy.</li>
</ul>
<p>Przykładowe kategorie rekordów:</p>
<p><strong>Pytanie i odpowiedź:</strong></p>
<blockquote>
<p><em>Jaka jest stolica Francji?</em></p>
<p><em>Stolicą Francji jest Paryż.</em></p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p><strong>Polecenie i odpowiedź:</strong></p>
<blockquote>
<p><em>Wymień trzy planety Układu Słonecznego.</em></p>
<p><em>Merkury, Wenus i Ziemia.</em></p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p><strong>Dialog:</strong></p>
<blockquote>
<p><em>Chyba zapomniałem zabrać parasola.</em></p>
<p><em>Może sprawdź prognozę pogody, zanim wyjdziesz.</em></p>
<p><code>&lt;|endoftext|&gt;</code></p>
</blockquote>
<p>Ważnym założeniem była <strong>samodzielność każdego rekordu</strong>. Pytanie lub polecenie musiało być zrozumiałe bez znajomości artykułu źródłowego czy pozostałych rekordów.</p>
<h3>3. Wybór i pozyskanie materiałów źródłowych</h3>
<p>Do przygotowania korpusu Toktumi 3.0 postanowiłem wybrać cztery główne źródła już zgromadzonych tekstów (zob. części o <a href="https://sixfingers.hashnode.dev/toktumi-1-pl"><strong>Toktumi 1.0</strong></a> i <a href="https://sixfingers.hashnode.dev/toktumi-2-pl"><strong>Toktumi 2.0</strong></a>): <strong>Wielki słownik języka polskiego (WSJP), Wikipedię, Fakt oraz napisy filmowe i serialowe</strong>.</p>
<p>Każde źródło miało rozwijać inne umiejętności modelu:</p>
<ul>
<li><strong>WSJP — język i znaczenia.</strong> Źródło danych zawierające definicje słów, związki frazeologiczne, typowe połączenia wyrazowe i przykłady użycia. Materiały miały poprawić rozumienie polskiego słownictwa i definiowanie pojęć.</li>
<li><strong>Wikipedia — wiedza.</strong> Artykuły encyklopedyczne dostarczające informacji o świecie i mające rozwijać umiejętność odpowiadania na pytania faktograficzne.</li>
<li><a href="https://www.fakt.pl/"><strong>Fakt.pl</strong></a> – <strong>wydarzenia.</strong> Artykuły prasowe dostarczające wiedzy o konkretnych wydarzeniach, ludziach i relacjach przyczynowo-skutkowych.</li>
<li><strong>Subtitles — rozmowa.</strong> Dialogi przygotowane na podstawie napisów do filmów miały nauczyć model prowadzenia naturalnej konwersacji oraz reagowania na emocje rozmówcy.</li>
</ul>
<p>Wygenerowane próbki wg źródeł:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d7a18e09-116d-44f4-939a-9bf482047aaa.png" alt="034_nowe_dane" style="display:block;margin:0 auto" />

<h3>4. Generowanie danych treningowych</h3>
<p>Do automatycznego tworzenia rekordów treningowych wykorzystałem przede wszystkim <strong>API Google Gemini</strong>. Od prac nad Toktumi 2.0 (2025) minął około rok. W tym czasie pojawiły się nowe, bardziej zaawansowane modele. Wcześniejszy <a href="https://aistudio.google.com/docs/models/gemini-2.5-flash"><strong>Gemini 2.5 Flash</strong></a> (obecnie wycofywany) zastąpiłem modelem <a href="https://aistudio.google.com/docs/models/gemini-3.1-flash-lite"><strong>Gemini 3.1 Flash-Lite</strong></a>.</p>
<p>Dodatkowo korzystałem z uruchamianego lokalnie modelu <a href="https://huggingface.co/unsloth/gemma-4-31B-it-GGUF"><strong>Gemma 4 31B</strong></a>, który zastąpił wcześniejszy <a href="https://huggingface.co/mistralai/Mistral-Nemo-Instruct-2407"><strong>Mistral NeMo 12B</strong></a>. W moich testach Gemma lepiej radziła sobie z językiem polskim niż równie popularny obecnie (2026) <a href="https://huggingface.co/Qwen/Qwen3.6-35B-A3B"><strong>Qwen3.6-35B-A3B</strong></a>.</p>
<p><strong>Mechanizm działania:</strong> skrypt w Pythonie przesyła przez API do modelu <strong>Gemini 3.1 Flash-Lite</strong> kolejne teksty źródłowe wraz z odpowiednim promptem, określającym zasady generowania. Na podstawie każdego tekstu model tworzy <strong>20 lub 30 rekordów treningowych</strong> w formie par Pytanie–Odpowiedź oraz Instrukcja–Odpowiedź. Gotowe rekordy są automatycznie zapisywane do plików txt.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/5cd018c5-e68b-4049-b48b-f295e7eab5de.png" alt="035_Generacja" style="display:block;margin:0 auto" />

<p>Przy Toktumi 3 po raz pierwszy wykorzystałem <strong>napisy filmowe (subtitles) jako źródło danych treningowych</strong>. Duży zbiór napisów zgromadziłem już wcześniej ze źródeł internetowych, jednak początkowe próby pokazały, że surowe teksty dialogów nie nadają się do uczenia — model generuje chaotyczne ciągi wypowiedzi, nie rozpoznając rozmówców ani momentu, w którym należy zakończyć wypowiedź.</p>
<p>Rozwiązaniem okazało się dopiero <strong>przetworzenie napisów w krótkie, samodzielne pary dialogowe</strong>, w których jedna wypowiedź stanowi logiczną reakcję na drugą. W tej formie napisy mogły posłużyć do uczenia prowadzenia prostej rozmowy.</p>
<p>Przetwarzanie fragmentu napisów w zestaw par treningowych:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/b60b2ab3-596b-4ea6-b4d1-c0c9bbd95eb8.png" alt="036_subtitles" style="display:block;margin:0 auto" />

<p>Kod i prompt do generowania danych działały na podobnej zasadzie jak w Toktumi 2.0 (zobacz <a href="https://sixfingers.hashnode.dev/toktumi-2-pl">Część 2</a>).</p>
<p>Rekordy wygenerowane ze <strong>wszystkich czterech źródeł</strong> wymagały sprawdzenia pod kątem autonomiczności. <strong>Każde pytanie lub polecenie musiało być zrozumiałe samodzielnie, bez znajomości artykułu źródłowego czy wcześniejszego kontekstu.</strong></p>
<p>Model automatycznie oceniał rekordy i oznaczał je jako <strong>1 – zostaw</strong> lub <strong>0 – odrzuć</strong>. W ten sposób eliminowałem pytania odwołujące się do nieznanych osób, wydarzeń czy wcześniejszych wypowiedzi.</p>
<p>Poniżej przykłady <strong>odrzuconych, nieautonomicznych rekordów wygenerowanych na podstawie artykułów newsowych.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/79bc17d9-350f-43c8-b862-bf99662bb802.png" alt="037_odrzucone" style="display:block;margin:0 auto" />

<h3>PRĘDKOŚĆ PRZETWARZANIA DANYCH</h3>
<p>Gemini przez API przetwarzał <strong>około 7–8 tekstów źródłowych na minutę</strong> (około 10 tys. na dobę), a lokalna <strong>Gemma 4 w wersji GGUF około 1–2 tekstów na minutę</strong> (około 2 tys. na dobę). Są to wartości szacunkowe.</p>
<p>Wygenerowanie i oczyszczenie wszystkich <strong>6 190 420 par treningowych</strong> zajęło <strong>około 2,5 miesiąca</strong>.</p>
<h3>5. Przygotowanie finalnego korpusu Toktumi 3.0</h3>
<p>Podobnie jak w poprzednich wersjach Toktumi, połączyłem przygotowane dane w <strong>jeden duży korpus treningowy</strong>. Przedtem <strong>wymieszałem wszystkie 6 190 420 rekordów</strong>, aby podczas uczenia przeplatały się pytania, instrukcje i dialogi. Następnie podzieliłem zbiór na <strong>99% danych treningowych (TRAIN) i 1% walidacyjnych (VALID)</strong>, oddzielając rekordy tokenem <code>&lt;|endoftext|&gt;</code> - rozpoznawanym przez model jako znacznik końca tekstu.</p>
<p>Tu drobna uwaga: pomimo usunięcia duplikatów, ze względu na losowy podział danych nie można wykluczyć, że w zbiorach TRAIN i VALID znalazły się pytania podobne tematycznie.</p>
<p>Syntetycznie wygenerowane teksty okazały się <strong>znacznie krótsze niż te używane w poprzednich wersjach Toktumi</strong>, co widać na poniższej ilustracji. Średnio jeden rekord miał około <strong>43 tokeny</strong>, a najdłuższy <strong>174 tokeny</strong>. Długość sekwencji treningowej (block size) ustawiłem na <strong>256 tokenów</strong>, dostosowując ją do takiej długości rekordów. Parametr ten ma również istotny wpływ na zużycie pamięci VRAM, więc w moim przypadku nie mógł być za wysoki.</p>
<p>Struktura zbioru uczącego z wymieszanymi tekstami:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a9d8535e-ba0e-4890-bc6b-e27fccab069c.png" alt="038_zbior_uczacy" style="display:block;margin:0 auto" />

<p>Porównanie rozmiarów zbiorów treningowych Toktumi 1.0 | Toktumi 2.0 | Toktumi 3.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/1b747d78-b5c5-4f2c-8689-242edb9246e4.png" alt="048_zbiory_treningowe_T1-T2-T3" style="display:block;margin:0 auto" />

<p>Porównanie z poprzednimi wersjami Toktumi pokazuje, jak bardzo zmienił się charakter zbioru treningowego. <strong>Toktumi 3.0 zawiera ponad 2,5 razy więcej rekordów niż Toktumi 2.0</strong>, a jednocześnie liczba słów i tokenów zmniejszyła się o około <strong>53% w stosunku do Toktumi 2.0</strong>. Sam plik treningowy jest niemal <strong>o połowę mniejszy</strong>.</p>
<p>Model więc uczył się na <strong>większej liczbie przykładów, ale przy znacznie mniejszej liczbie tokenów</strong>. Tym razem postawiłem przede wszystkim na <strong>jakość, zwięzłość i uporządkowanie danych treningowych</strong>, zamiast zwiększania ich objętości.</p>
<p>Długość tekstów w zbiorach treningowych dobrze ilustruje poniższy wykres:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/ac7498e7-fdc3-46f2-a653-44fe10b0f71c.png" alt="049_dlogosc_boxploty" style="display:block;margin:0 auto" />

<p>Boxplot pokazuje, jak bardzo skróciły się rekordy treningowe w kolejnych wersjach Toktumi.</p>
<p><strong>Mediana długości: Toktumi 1.0 – 207 tokenów, Toktumi 2.0 – 145 tokenów, Toktumi 3.0 – zaledwie 41 tokenów.</strong></p>
<p>Przy okazji na wykresie widać dwie najbardziej skrajne wartości odstające w Toktumi 1.0 i 2.0 (pojedyncze kropki z prawej strony) - czyli bardzo długie teksty, których nie wychwyciły stosowane przeze mnie na wcześniejszych etapach filtry długości. Mogą zawyżać średnią, dlatego jako miarę długości wybrałem medianę.</p>
<p>W Toktumi 3.0 rekordy powstawały od podstaw, dzięki czemu teksty mają bardziej ustandaryzowaną długość.</p>
<h3>6. Trening Toktumi 3.0</h3>
<p>Przed właściwym treningiem przeprowadziłem eksperyment na mniejszym zbiorze danych, aby sprawdzić, czy lepiej uczyć model od zera, czy kontynuować uczenie Toktumi 2.0. <strong>Testy wykazały przewagę fine-tuningu</strong>, dlatego zdecydowałem się wykorzystać wcześniej wyuczone umiejętności językowe modelu.</p>
<p>Zachowałem <strong>architekturę GPT-2 Medium oraz tokenizer Toktumi 2.0</strong>, aby zapewnić zgodność pomiędzy obu wersjami.</p>
<p>Trening zaplanowałem na <strong>maksymalnie 15 epok</strong>, z automatyczną walidacją, zapisywaniem checkpointów i mechanizmem <strong>early stopping</strong>, który miał zakończyć uczenie, gdy model przestanie się poprawiać.</p>
<p>Kluczowa część kodu użytego do uczenia modelu:</p>
<pre><code class="language-python"># Wczytanie Toktumi 2 i tokenizera
tokenizer = GPT2TokenizerFast.from_pretrained(TOKENIZER_PATH)
model = GPT2LMHeadModel.from_pretrained(MODEL_PATH)

# Przygotowanie danych
train_dataset = TokenBlockDataset(TRAIN_PATH, tokenizer, 256) # block_size = 256
valid_dataset = TokenBlockDataset(VALID_PATH, tokenizer, 256)

# Parametry treningu
training_args = TrainingArguments(
    output_dir=str(CHECKPOINT_DIR),
    num_train_epochs=15,
    per_device_train_batch_size=6,
    gradient_accumulation_steps=4,
    learning_rate=5e-5,
    fp16=True,
    eval_strategy="steps",
    eval_steps=8000,
    save_strategy="steps",
    save_steps=8000,
    save_total_limit=100,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
)

# Fine-tuning z mechanizmem early stopping
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset,
    data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
    callbacks=[EarlyStoppingCallback(early_stopping_patience=6)],
)

trainer.train()
trainer.save_model(str(BEST_MODEL_DIR))
</code></pre>
<p>Zdecydowałem się <strong>zachować wszystkie checkpointy</strong>, co pomogło później porównać jakość odpowiedzi modelu na różnych etapach treningu.</p>
<p>Bogatszy o wcześniejsze doświadczenia, tym razem postanowiłem zastosować callback <strong>early stopping</strong>, zamiast ręcznie oceniać postępy na podstawie perplexity. Jak się okazało, była to dobra decyzja – nie musiałem już samodzielnie decydować, kiedy zakończyć trening. Z dużego zbioru danych wydzieliłem odpowiednio liczny zbiór walidacyjny, a mechanizm early stopping automatycznie zatrzymywał uczenie, gdy wartość <code>eval_loss</code> przestawała się poprawiać. <strong>Jeden cykl walidacji trwał około 3 minut</strong>, więc nie wydłużało to znacząco całego procesu.</p>
<h3>TRENING W LICZBACH</h3>
<p>Ze względu na wysokie ceny kart graficznych (2026) zdecydowałem się pozostać przy używanej do tej pory NVIDIA GeForce RTX 3060. Parametry treningu dopasowałem do dostępnej pamięci (12 GB VRAM).</p>
<ul>
<li><strong>Jedna epoka:</strong> składała się z <strong>44 538 kroków</strong>, a przejście przez 1 epokę zajmowało <strong>ok 16 godzin</strong>.</li>
<li><strong>Czas całego treningu:</strong> około <strong>9,5 dnia</strong>, z przerwami i wznawianiem z checkpointów.</li>
<li><strong>Early stopping:</strong> automatycznie zakończył trening na <strong>448 000. kroku, po 10,06 epoki</strong>.</li>
<li><strong>Najlepszy checkpoint</strong> według eval_loss: <strong>checkpoint-400000</strong>, osiągnięty pod koniec 9. epoki (8,98).</li>
<li><strong>Zapisane checkpointy:</strong> łącznie <strong>56</strong> (w dalszych testach wykorzystuję połowę z nich).</li>
</ul>
<p>Wykres ilustrujący postęp treningu:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/de1681ba-b17e-4598-a99b-6538ec40774e.png" alt="040_uczenie_wykres" style="display:block;margin:0 auto" />

<p>Po dziewiątej epoce <strong>train_loss nadal spadał, ale eval_loss przestał się poprawiać</strong>, co ostatecznie uruchomiło mechanizm <strong>early stopping</strong>.</p>
<p>Charakterystyczne „schodki” na wykresie train_loss (niebieska linia) pojawiają się regularnie na początku kolejnych epok. Nie muszą świadczyć o błędzie treningu, a jak można przypuszczać, mają związek z ponownym przemieszaniem danych treningowych i przebiegiem procesu optymalizacji.</p>
<h3>7. Ocena jakości modelu — PROMPTING i testowanie checkpointów</h3>
<p>W kolejnym kroku przetestowałem <strong>29 wybranych checkpointów</strong> na <strong>dwóch dodatkowych zestawach testowych po 300 promptów</strong>, uzyskując łącznie <strong>17 400 odpowiedzi</strong> — co stanowi obszerny materiał do analizy.</p>
<p>Z pomocą AI przeprowadziłem szacunkową ocenę <strong>FCT (Factuality)</strong>, uwzględniając poprawność faktograficzną, sensowność i adekwatność odpowiedzi. Każda poprawna lub sensowna odpowiedź otrzymywała 1 punkt. Jest to umowne kryterium (szczegółowo opisałem je w <a href="https://sixfingers.hashnode.dev/toktumi-2-pl">Części 2</a>), które pozwoliło porównać jakość odpowiedzi poszczególnych checkpointów.</p>
<p><strong>Najlepsze wyniki (600 promptów):</strong></p>
<ul>
<li><strong>checkpoint-400000:</strong> 431/600 (71,8%)</li>
<li><strong>checkpoint-448000:</strong> 431/600 (71,8%)</li>
<li><strong>checkpoint-432000:</strong> 425/600 (70,8%)</li>
</ul>
<p><strong>Do dalszych testów wybrałem checkpoint-400000</strong>, który osiągnął również najniższy <code>eval_loss</code> podczas treningu i został automatycznie wskazany jako najlepszy przez mechanizm early stopping. Należy dodać, że różnice między najlepszymi checkpointami były niewielkie.</p>
<p>Wyniki checkpointów według kryterium Factuality (FCT) czyli poprawność, zgodność z faktami i sensowność odpowiedzi:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/63751c30-3212-4500-b8bd-05fe3bbf6979.png" alt="050_find_best_chckp" style="display:block;margin:0 auto" />

<p><strong>Ciekawostka:</strong> Na pytanie o pierwszego prezydenta USA (w trybie greedy decoding) checkpoint-384000 poprawnie wskazał George'a Washingtona, checkpoint-400000 błędnie podał George'a W. Busha, a checkpoint-448000 znów odpowiedział prawidłowo. Pokazuje to, że <strong>dłuższy trening nie zawsze gwarantuje poprawę odpowiedzi</strong>. Model potrafi udzielić prawidłowej odpowiedzi, ale nie zawsze wybiera ją jako najbardziej prawdopodobną.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/19bee6a6-d90d-4c03-8009-d2e450eb756d.png" alt="041_prezydent" style="display:block;margin:0 auto" />

<p>Zestawy promptów wraz z odpowiedziami w podziale na checkpointy - do obejrzenia.</p>
<p><a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_3.0_29_checkpoints_300_prompts_set_1.txt"><strong>300 Pack 1</strong></a> oraz drugi zestaw <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_3.0_29_checkpoints_300_prompts_set_2.txt"><strong>300 Pack 2</strong></a>.</p>
<h3>8. Porównanie wszystkich wersji modelu: Toktumi 1-2-3</h3>
<p>W tym miejscu <strong>sprawdzam, jak Toktumi 3.0 wypada na tle poprzednich wersji (opisanych w</strong> <a href="https://sixfingers.hashnode.dev/toktumi-1-pl"><strong>Części 1</strong></a> <strong>i</strong> <a href="https://sixfingers.hashnode.dev/toktumi-2-pl"><strong>Części 2</strong></a><strong>)</strong>. Wracam do zestawu <strong>150 promptów</strong>, wykorzystanego wcześniej (weryfikacja Toktumi 1.0 i 2.0).</p>
<p>Test pokazuje, <strong>czy Toktumi 3.0 poprawnie rozpoznaje polecenie (IF)</strong>, a następnie <strong>czy jego odpowiedź jest zgodna z faktami (gdy można zastosować kryterium prawdziwości) lub sensowna (bardziej subiektywna ocena w przypadku dialogów) (FCT)</strong>. Wyniki klasyfikowałem jako <strong>TAK/TAK, TAK/HAL lub NIE</strong>, gdzie <strong>HAL</strong> oznacza halucyowanie, pomimo prawidłowego rozpoznania konstrukcji wypowiedzi (szczegóły – w części o <a href="https://sixfingers.hashnode.dev/toktumi-2-pl"><strong>Toktumi 2.0</strong></a>).</p>
<h3>Wyniki Toktumi 1.0</h3>
<table>
<thead>
<tr>
<th>Klasyfikacja [IF / FCT]</th>
<th align="right">Liczba przypadków</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TAK / TAK</strong> – poprawna struktura i poprawna odpowiedź</td>
<td align="right">85</td>
</tr>
<tr>
<td><strong>TAK / HAL</strong> – poprawna struktura, ale halucynacja</td>
<td align="right">33</td>
</tr>
<tr>
<td><strong>NIE</strong> – brak poprawnego rozpoznania polecenia</td>
<td align="right">32</td>
</tr>
<tr>
<td><strong>Razem</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Odsetek poprawnych odpowiedzi (TAK / TAK)</strong></td>
<td align="right"><strong>56,7%</strong></td>
</tr>
</tbody></table>
<h3>Wyniki Toktumi 2.0</h3>
<table>
<thead>
<tr>
<th>Klasyfikacja [IF / FCT]</th>
<th align="right">Liczba przypadków</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TAK / TAK</strong> – poprawna struktura i poprawna odpowiedź</td>
<td align="right">102</td>
</tr>
<tr>
<td><strong>TAK / HAL</strong> – poprawna struktura, ale halucynacja</td>
<td align="right">42</td>
</tr>
<tr>
<td><strong>NIE</strong> – brak poprawnego rozpoznania polecenia</td>
<td align="right">6</td>
</tr>
<tr>
<td><strong>Razem</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Odsetek poprawnych odpowiedzi (TAK / TAK)</strong></td>
<td align="right"><strong>68,0%</strong></td>
</tr>
</tbody></table>
<h3>Wyniki Toktumi 3.0</h3>
<table>
<thead>
<tr>
<th>Klasyfikacja [IF / FCT]</th>
<th align="right">Liczba przypadków</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TAK / TAK</strong> – poprawna struktura i poprawna odpowiedź</td>
<td align="right">114</td>
</tr>
<tr>
<td><strong>TAK / HAL</strong> – poprawna struktura, ale halucynacja</td>
<td align="right">31</td>
</tr>
<tr>
<td><strong>NIE</strong> – brak poprawnego rozpoznania polecenia</td>
<td align="right">5</td>
</tr>
<tr>
<td><strong>Razem</strong></td>
<td align="right"><strong>150</strong></td>
</tr>
<tr>
<td><strong>Odsetek poprawnych odpowiedzi (TAK / TAK)</strong></td>
<td align="right"><strong>76,0%</strong></td>
</tr>
</tbody></table>
<p><strong>Wyniki pokazują wyraźną poprawę jakości modelu.</strong> Odsetek poprawnych odpowiedzi wzrósł z <strong>56,7% w Toktumi 1.0 do 76% w Toktumi 3.0</strong>, a liczba nierozpoznanych poleceń spadła z <strong>32 do 5 na 150 promptów</strong>.</p>
<p>Toktumi 3.0 znacznie lepiej radzi sobie z poleceniami i częściej udziela poprawnych odpowiedzi, choć nadal popełnia błędy faktograficzne i generuje halucynacje.</p>
<p><strong>Wyniki z odpowiedziami dla Toktumi 1-2-3 (150 promptów)</strong> <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_1-2-3_150_prompts_comparison.txt"><strong>TUTAJ</strong></a></p>
<hr />
<h3>9. Testy jakościowe, czyli co potrafi model Toktumi 3.0?</h3>
<p>W tym miejscu dokładnie sprawdzam <strong>możliwości i ograniczenia Toktumi 3.0</strong>.</p>
<p>Wykorzystałem dwa przygotowane wcześniej zestawy po 300 promptów (<strong>300 Pack 1 i 300 Pack 2</strong>). Wszystkie <strong>600 odpowiedzi</strong> wygenerowałem w trybie <strong>greedy decoding</strong>.</p>
<p>Wyniki i wnioski poniżej.</p>
<hr />
<h4>a) FORMA: KRÓTSZE ODPOWIEDZI</h4>
<p>Pierwsza zauważalna zmiana to sposób, w jaki model kończy wypowiedzi. W przeciwieństwie do wcześniejszych generacji, Toktumi 3.0 zazwyczaj udziela <strong>krótkiej odpowiedzi</strong> i nie kontynuuje niepotrzebnie tekstu. Zostały też znacznie ograniczone zapętlenia tekstu, charakterystyczne dla Toktumi 1 i 2.</p>
<p>Przykład <strong>zapętlenia tekstu</strong> - problem znacznie ograniczony w Toktumi 3. Ostatnia wersja modelu znacznie rzadziej wpada w powtórzenia i częściej udziela krótkich, poprawnych i spójnych odpowiedzi na zadany temat.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/84857d03-a8b3-4483-afae-825868aa1005.png" alt="042_zapetlenie" style="display:block;margin:0 auto" />

<p><strong>Krótkie odpowiedzi</strong> to znak rozpoznawczy Toktumi 3.0:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, nr 7):</strong> <em>W jakiej dyscyplinie sportu specjalizowała się Justyna Kowalczyk?</em></p>
<p><strong>Odpowiedź:</strong> <em>Biegi narciarskie.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 90):</strong> <em>Jak nazywa się jednostka dziedziczenia znajdująca się w DNA?</em></p>
<p><strong>Odpowiedź:</strong> <em>Gen.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 43):</strong> <em>Jak nazywa się naturalny satelita naszej planety?</em></p>
<p><strong>Odpowiedź:</strong> <em>Księżyc.</em></p>
</blockquote>
<hr />
<h4>b) CO MODEL ROBI DOBRZE?</h4>
<p><strong>OK 1. Proste pytania i wykonywanie instrukcji</strong></p>
<p>Model dobrze radzi sobie z poleceniami, które wymagają wskazania konkretnych przykładów, stworzenia krótkiej listy lub udzielenia jednoznacznej odpowiedzi.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, nr 4):</strong> <em>Wymień trzy rzeczy, które przydają się podczas pikniku.</em></p>
<p><strong>Odpowiedź:</strong> <em>Podczas pikniku przydają się: koc, napoje oraz przekąski.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 100):</strong> <em>Wymień trzy polskie owoce sezonowe.</em></p>
<p><strong>Odpowiedź:</strong> <em>Truskawki, maliny, jagody.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 142):</strong> <em>Wymień dwa sposoby podróżowania po mieście bez własnego samochodu.</em></p>
<p><strong>Odpowiedź:</strong> <em>Dwa sposoby to korzystanie z komunikacji miejskiej, takiej jak autobusy lub tramwaje, oraz chodzenie pieszo.</em></p>
</blockquote>
<hr />
<p><strong>OK 2. Definicje i wyjaśnianie podstawowych pojęć</strong></p>
<p>Toktumi 3 potrafi też <strong>formułować poprawne, rozbudowane wyjaśnienia</strong>:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, nr 34):</strong> <em>Wyjaśnij prostymi słowami, do czego służy termostat.</em></p>
<p><strong>Odpowiedź:</strong> <em>Termostat to urządzenie, które włącza lub wyłącza grzanie, gdy temperatura w pomieszczeniu spadnie poniżej ustawionego poziomu, co pozwala utrzymać w nim komfortową temperaturę.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 106):</strong> <em>Opisz, czym jest szczepionka i jaki jest cel jej stosowania.</em></p>
<p><strong>Odpowiedź:</strong> <em>Szczepionka to preparat biologiczny, który uczy układ odpornościowy rozpoznawać i zwalczać konkretne drobnoustroje, takie jak wirusy czy bakterie, chroniąc przed zachorowaniem lub łagodząc przebieg choroby.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 125):</strong> <em>Opisz, na czym polega funkcja Rzecznika Praw Obywatelskich w Polsce.</em></p>
<p><strong>Odpowiedź:</strong> <em>Rzecznik Praw Obywatelskich stoi na straży wolności i praw człowieka oraz obywatela, badając, czy organy władzy publicznej działają zgodnie z prawem i czy nie przekraczają swoich uprawnień.</em></p>
</blockquote>
<p>W takich przykładach model generuje <strong>spójną wypowiedź</strong>, używając właściwego słownictwa i zachowując logiczną strukturę.</p>
<p>Wreszcie też prawidłowo podaje <strong>definicję ołówka</strong>, z czym poprzednie wersje Toktumi miewały problem.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/ffb3de45-a084-4df2-9a8d-562e8697f4d2.png" alt="043_olowek" style="display:block;margin:0 auto" />

<hr />
<p><strong>OK 3. Rozumienie słów i frazeologizmów</strong></p>
<p>Ciekawe są również odpowiedzi dotyczące <strong>znaczenia polskich słów i związków frazeologicznych</strong>, których model uczył się na podstawie słownika języka polskiego.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, nr 22):</strong> <em>Wyjaśnij słowo „zawzięty” w odniesieniu do osoby dążącej do celu.</em></p>
<p><strong>Odpowiedź:</strong> <em>Oznacza to, że osoba ta jest bardzo zdeterminowana, nie poddaje się łatwo i z wielkim zapałem realizuje swoje zamierzenia, nie zważając na przeszkody.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 292):</strong> <em>Co znaczy frazeologizm „grać na zwłokę”, gdy ktoś unika podjęcia decyzji?</em></p>
<p><strong>Odpowiedź:</strong> <em>Oznacza to celowe opóźnianie jakiegoś działania lub decyzji, aby zyskać na czasie i uniknąć podjęcia właściwego wyboru.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 218):</strong> <em>Jak rozumiesz „rzucać kłody pod nogi”?</em></p>
<p><strong>Odpowiedź:</strong> <em>To określenie sytuacji, w której ktoś celowo utrudnia innym osiągnięcie zamierzonego celu.</em></p>
</blockquote>
<p>Co istotne, model potrafi nie tylko podać znaczenie, ale również wyjaśnić je innymi słowami niż w definicji.</p>
<hr />
<p><strong>OK 4. Krótkie, naturalne dialogi</strong></p>
<p>W Toktumi 3.0 po raz pierwszy wykorzystałem <strong>napisy filmowe (subtitles)</strong>, przetworzone w samodzielne pary dialogowe. Poniższe przykłady pokazują, że model teraz potrafi dopasować odpowiedź do sytuacji i ograniczyć się do jednej wypowiedzi - jak w prawdziwej rozmowie.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, nr 230):</strong> <em>Przepraszam za spóźnienie, autobus utknął w korku.</em></p>
<p><strong>Odpowiedź:</strong> <em>Nie szkodzi, najważniejsze, że już jesteś i możemy zaczynać.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 172):</strong> <em>Obiecaj mi, że zachowasz to dla siebie.</em></p>
<p><strong>Odpowiedź:</strong> <em>Możesz być spokojny, nikomu nie pisnę ani słowa.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 238):</strong> <em>Wreszcie dostałem tę pracę, o której ci opowiadałem!</em></p>
<p><strong>Odpowiedź:</strong> <em>To fantastyczna wiadomość, naprawdę się cieszę, że udało ci się ją dostać.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 219):</strong> <em>Nie uwierzysz, co mi się dzisiaj przydarzyło!</em></p>
<p><strong>Odpowiedź:</strong> <em>Co takiego?</em></p>
</blockquote>
<p>Niektóre reakcje są zaskakująco naturalne. Model potrafi rozpoznać emocjonalny ton rozmowy, wyrazić zainteresowanie lub odpowiedzieć potocznym językiem.</p>
<hr />
<h4>c) Z CZYM MODEL NADAL MA PROBLEMY?</h4>
<p><strong>Problem 1. Wiedza faktograficzna — poprawna forma, ale błędna treść</strong></p>
<p>Największym problemem pozostają szczegółowe fakty: nazwiska, daty, wydarzenia historyczne i konkretne informacje o świecie.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v3, nr 54):</strong> <em>Kto był pierwszym prezydentem Stanów Zjednoczonych?</em></p>
<p><strong>Odpowiedź:</strong> <em>George W. Bush.</em></p>
<p>(Powinno być: <em>George Washington.</em>)</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v3, nr 16):</strong> <em>Podaj imię i nazwisko pierwszej kobiety, która poleciała w kosmos.</em></p>
<p><strong>Odpowiedź:</strong> <em>Jurij Gagarin.</em></p>
<p>(Powinno być: <em>Walentina Tierieszkowa.</em>)</p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 1):</strong> <em>Podaj nazwę norweskiej stolicy.</em></p>
<p><strong>Odpowiedź:</strong> <em>Stolicą Norwegii jest Bermudy.</em></p>
<p>(Powinno być: <em>Oslo.</em>)</p>
</blockquote>
<p>Te przykłady dobrze pokazują, że <strong>model opanował formę odpowiadania znacznie lepiej niż wiedzę faktograficzną</strong>. Nie odmawia odpowiedzi i nie sygnalizuje niepewności — po prostu <strong>podaje nieprawdziwą informację (halucynuje).</strong></p>
<p>Powyższe odpowiedzi uzyskano w trybie <strong>greedy decoding</strong>. Po wprowadzeniu losowania <strong>top_k=2</strong> model potrafił również wskazać poprawne odpowiedzi: <strong>George’a Washingtona w 3 z 10 prób</strong>, a <strong>Oslo w 6 z 20 prób</strong>.</p>
<p>Wystarczy kilka prób i model w końcu prawidłowo podaje stolicę Norwegii.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d4c3d4a0-921d-4cd3-94d2-703bf57305b2.png" alt="044_Oslo" style="display:block;margin:0 auto" />

<p>Jednak losowanie nie zawsze pomaga. W przypadku pytania o <strong>pierwszą kobietę w kosmosie</strong> model ani razu na <strong>100 prób</strong> nie wskazał <strong>Walentiny Tierieszkowej</strong>. Zamiast tego podawał <a href="http://m.in">m.in</a>. Jurija Gagarina, Marię Skłodowską-Curie, projektantkę mody Jil Sander, aktorkę Gillian Anderson, a nawet Marię Czubaszek.</p>
<p>Przeszukałem zbiór uczący - okazuje się, że ta informacja pojawiła się w datasecie kilkukrotnie.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/19204fb5-a903-45cd-a1b5-91d1c622a4b0.png" alt="045_Tierieszkowa" style="display:block;margin:0 auto" />

<p>Postanowiłem więc <strong>przeformułować pytanie</strong> na <em>„Kim była Walentina Tierieszkowa?”</em>. Tym razem model odpowiada poprawnie:</p>
<blockquote>
<p><em>„Była radziecką kosmonautką, która jako pierwsza kobieta w historii poleciała w kosmos."</em></p>
</blockquote>
<p>Jak widać, model <strong>zna tę informację, ale ma problem z powiązaniem jej z właściwą osobą</strong>.</p>
<hr />
<p><strong>Problem 2. Model potrafi 'zepsuć' poprawną odpowiedź, dopisując błędne wyjaśnienie</strong></p>
<p>To szczególnie interesujący rodzaj halucynacji.</p>
<blockquote>
<p><strong>Prompt (v3, nr 1):</strong> <em>Wskaż, nad jaką rzeką położony jest Sandomierz.</em></p>
<p><strong>Odpowiedź:</strong> <em>Sandomierz leży nad rzeką Wisłą, która jest dopływem Wisły.</em></p>
</blockquote>
<p>Pierwsza część odpowiedzi jest poprawna. Druga — bez sensu.</p>
<p>Inny przykład - pytanie o fotosyntezę:</p>
<blockquote>
<p><strong>Prompt (v3, nr 43):</strong> <em>Jaki gaz rośliny pobierają z atmosfery w procesie fotosyntezy?</em></p>
<p><strong>Odpowiedź:</strong> <em>W procesie fotosyntezy z atmosfery pobierają dwutlenek węgla, który jest niezbędny do oddychania dla większości roślin.</em></p>
</blockquote>
<p>Model prawidłowo wskazuje dwutlenek węgla, ale błędnie wyjaśnia jego funkcję.</p>
<hr />
<p><strong>Problem 3. Rozumienie znaczenia nie zawsze idzie w parze ze znajomością języka</strong></p>
<p>Mimo wielu udanych definicji model ma problemy z niektórymi, nawet popularnymi wyrażeniami.</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt (v4, nr 134):</strong> <em>Co oznacza „mieć dwie lewe ręce”?</em></p>
<p><strong>Odpowiedź:</strong> <em>Oznacza to, że osoba wykonująca pracę ma dwie ręce jednocześnie, co często zdarza się w zawodach wymagających precyzji lub w sytuacjach wymagających dużej siły fizycznej.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt (v4, nr 296):</strong> <em>Wyjaśnij pojęcie „syzyfowa praca”.</em></p>
<p><strong>Odpowiedź:</strong> <em>Jest to określenie sytuacji, w której ktoś wykonuje swoje zadania w sposób bardzo powolny, bez wkładania w nie żadnego wysiłku, co prowadzi do szybkiego zakończenia pracy.</em></p>
</blockquote>
<p>W pierwszym przypadku model interpretuje frazeologizm niemal dosłownie. W drugim tworzy płynne wyjaśnienie, które jednak jest halucynacją i nie oddaje właściwego znaczenia słownikowego.</p>
<p>Po sprawdzeniu zbioru treningowego okazało się, że zwrot <strong>„mieć dwie lewe ręce”</strong> występował w zbiorze uczącym 40 razy, a <strong>„syzyfowa praca”</strong> 58 razy - wśród nich były zarówno definicje, jak i użycia w dialogach w różnych kontekstach. W tym przypadku również wystarczyło przeformułować prompt - i od razu pojawiły się prawidłowe odpowiedzi:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co potocznie oznacza zwrot mieć dwie lewe ręce?</em></p>
<p><strong>Odpowiedź:</strong> <em>Oznacza to, że ktoś jest niezdarny, mało sprawny fizycznie lub nie radzi sobie z wykonywaniem prostych czynności manualnych.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest syzyfowa praca?</em></p>
<p><strong>Odpowiedź:</strong> <em>Syzyfowa praca to określenie na bardzo ciężką, żmudną i wymagającą wysiłku pracę, która nie przynosi żadnych widocznych efektów.</em></p>
</blockquote>
<p>Wniosek jest podobny jak w przypadku Tierieszkowej: model przyswoił wiedzę, ale nie zawsze potrafi wskazać prawidłową odpowiedź jako najbardziej oczywistą. <strong>Czasem wystarczy odpowiednio przeformułować prompt lub zadać pytanie inaczej</strong>, aby osiągnąć zamierzony efekt.</p>
<hr />
<p><strong>Problem 4. Codzienne sytuacje i wiedza zdroworozsądkowa</strong></p>
<blockquote>
<p><strong>Prompt (v4, nr 201):</strong> <em>Co zrobić, gdy na kuchennej podłodze leżą odłamki rozbitej szklanki?</em></p>
<p><strong>Odpowiedź:</strong> <em>Należy niezwłocznie wezwać pogotowie ratunkowe, dzwoniąc pod numer alarmowy 112, a następnie zabezpieczyć miejsce zdarzenia i nie próbować samodzielnie naprawiać rozbitych szklanek czy talerzy.</em></p>
</blockquote>
<p>Model rozpoznaje, że chodzi o bezpieczeństwo, ale proponuje działanie zupełnie nieproporcjonalne do sytuacji.</p>
<p>Jeszcze jeden przykład:</p>
<blockquote>
<p><strong>Prompt (v4, nr 263):</strong> <em>Kupiłem aparat fotograficzny. Od czego zacząć naukę robienia zdjęć?</em></p>
<p><strong>Odpowiedź:</strong> <em>Najpierw naucz się obsługi miksera, potem spróbuj zrobić kilka zdjęć w różnych warunkach oświetleniowych, a na koniec spróbuj zrobić kilka ujęć w różnych miejscach, aby sprawdzić, jak najlepiej ustawić aparat.</em></p>
</blockquote>
<p>Odpowiedź zaczyna się od przypadkowego zalecenia dotyczącego miksera, po czym wraca do właściwego tematu fotografowania, choć robi to w sposób dość chaotyczny.</p>
<p>Warto jednak zauważyć, że <strong>Toktumi 3 potrafi również udzielać rozsądnych porad dotyczących codziennych sytuacji</strong>. Przykładowo:</p>
<blockquote>
<p><strong>Prompt (v4, nr 93):</strong> <em>Dlaczego nie wolno dotykać przewodów wystających z uszkodzonego gniazdka?</em></p>
<p><strong>Odpowiedź:</strong> <em>Dotykanie przewodów elektrycznych może doprowadzić do zwarcia, awarii instalacji lub porażenia prądem, dlatego należy zachować ostrożność.</em></p>
</blockquote>
<p>Przykłady pokazują, że <strong>wiedza zdroworozsądkowa nadal sprawia modelowi trudności</strong>. Toktumi 3.0 potrafi czasem udzielić trafnej, praktycznej porady, ale zdarza mu się również proponować działania nieproporcjonalne do sytuacji lub łączyć informacje, które do siebie nie pasują.</p>
<hr />
<p><strong>Problem 5. Naturalna rozmowa to nie zawsze zrozumienie intencji</strong></p>
<p>Choć model dobrze radzi sobie z krótkimi reakcjami dialogowymi, gorzej wypada, gdy rozmówca oczekuje określonego działania.</p>
<blockquote>
<p><strong>Prompt (v4, nr 275):</strong> <em>Sąsiad zaprosił nas na grill, ale nie możemy przyjść. Jak grzecznie odmówić?</em></p>
<p><strong>Odpowiedź:</strong> <em>- Jasne, nie ma problemu.</em></p>
</blockquote>
<p>Zamiast zaproponować uprzejmą odmowę, model generuje krótką odpowiedź dialogową, która nie realizuje polecenia.</p>
<p>Podobnie w sytuacji wymagającej porady:</p>
<blockquote>
<p><strong>Prompt (v4, nr 83):</strong> <em>Muszę jutro wcześnie wstać. Jak się przygotować, żeby nie zaspać?</em></p>
<p><strong>Odpowiedź:</strong> <em>Zacznij od szklanki wody, to zawsze pomaga na żołądek.</em></p>
</blockquote>
<p>Model odpowiada płynnie, ale nie udziela porady, jakiej zapewne oczekiwał rozmówca.</p>
<p>Problem może wynikać ze sposobu przygotowania <strong>krótkich, autonomicznych par dialogowych</strong>, które nie uwzględniają szerszego kontekstu rozmowy. Model nauczył się naturalnie reagować na pojedyncze wypowiedzi, ale nie zawsze prawidłowo rozpoznaje <strong>intencje i role rozmówców</strong>.</p>
<p>Rozwiązaniem mogłoby być <strong>uczenie modelu na dłuższych, wieloetapowych dialogach</strong>, zachowujących kontekst i role rozmówców. Wymagałoby to jednak bardziej złożonego przygotowania danych.</p>
<hr />
<h4>d) DODATKOWE CIEKAWOSTKI</h4>
<h3>Ciekawostka 1. Skąd się biorą dzieci? — pytanie, które wprawia w zakłopotanie</h3>
<p>Postanowiłem sprawdzić, jak Toktumi 3 radzi sobie z pozornie banalnym pytaniem: <strong>„Skąd się biorą dzieci?”</strong>. Przetestowałem <strong>29 checkpointów</strong> (połowę zapisanych), aby zobaczyć, jak odpowiedzi zmieniały się w trakcie treningu. Wynik był zaskakujący — <strong>żaden z checkpointów nie udzielił poprawnej odpowiedzi</strong>. Zamiast wyjaśniać biologię, model zachowywał się niemal jak zakłopotany dorosły, próbujący uniknąć niewygodnego tematu:</p>
<p>[Toktumi 3.0]</p>
<blockquote>
<p><strong>Checkpoint-48000:</strong> <em>To nie jest pytanie, na które chciałbyś usłyszeć odpowiedź.</em></p>
<p><strong>Checkpoint-80000:</strong> <em>To nie jest temat do rozmowy przy śniadaniu.</em></p>
<p><strong>Checkpoint-272000:</strong> <em>To pytanie, na które nie dostaniesz odpowiedzi.</em></p>
<p><strong>Checkpoint-400000:</strong> <em>Nie mam pojęcia, ale na pewno nie są to dzieci, o których teraz wspominasz.</em></p>
</blockquote>
<p>Co ciekawe, <strong>wystarczyło zapytać inaczej</strong>. Na pytanie <em>„Jak powstają dzieci?”</em> odpowiadał już prawidłowo:</p>
<blockquote>
<p><em>Powstają one w wyniku zapłodnienia komórki jajowej przez plemnik, co prowadzi do powstania zygoty.</em></p>
</blockquote>
<p>Odpowiedź jest bardziej charakterystyczna dla języka biologii i medycyny niż codziennej rozmowy, jednak należy ją uznać za poprawną.</p>
<p>To ciekawy przykład <strong>odwzorowania językowego obrazu świata</strong> — model naśladuje nie tylko naszą wiedzę, ale również sposób myślenia i skojarzenia utrwalone w języku.</p>
<hr />
<h3>Ciekawostka 2. Czy Bóg istnieje? - metafizyczne rozterki sztucznej inteligencji</h3>
<p>Kolejnym eksperymentem było pytanie <strong>„Czy Bóg istnieje?”</strong>, które zadałem wszystkim <strong>29 checkpointom</strong>, aby sprawdzić, jak odpowiedzi zmieniały się wraz z postępem uczenia. Wyniki były zaskakujące — Toktumi <strong>wielokrotnie zmienia odpowiedź z „tak” na „nie” i odwrotnie</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/767d606a-6a7b-4abb-801e-74d64e6ac333.png" alt="046_czy_bog_istnieje" style="display:block;margin:0 auto" />

<p>W miarę postępu treningu można zauważyć pewną prawidłowość: <strong>początkowo przeważało „nie”, w środkowej fazie zdecydowanie „tak”, a pod koniec ponownie „nie”</strong>. Można więc powiedzieć półżartem, że Toktumi wraz z postępem uczenia najpierw <strong>„nabywał wiarę”, a następnie ją „tracił”</strong>!</p>
<hr />
<h3>Ciekawostka 3. Sztuczna inteligencja uczy się przeklinać</h3>
<p>W słowniku języka polskiego znajdziemy również wyrazy <strong>wulgarne i pejoratywne</strong> — to naturalna część polszczyzny. Obok mocnych wulgaryzmów występują tam określenia takie jak „patafian”, „łajza”, „zadupie”, a także zwroty „zakuty łeb” czy „dupa wołowa”.</p>
<p>Podczas przetwarzania takich haseł Gemini często blokował generowanie odpowiedzi ze względu na mechanizmy moderacji. Dlatego wykorzystałem mniej restrykcyjny lokalny model Gemma, aby również z tego materiału przygotować pary pytań i instrukcji.</p>
<p>Sprawdzamy, czy Toktumi potrafi przeklinać. Jak się okazuje, model <strong>przyswoił sobie zasób wulgarnego słownictwa</strong>, ale czasem miesza dosadne przekleństwa z zupełnie łagodnymi powiedzeniami:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wymień znane ci przekleństwa.</em></p>
<p><strong>Odpowiedź:</strong> <em>Do popularnych przekleństw należą: „kurwa mać”, „spadaj” oraz „srali muchy będzie wiosna”.</em></p>
</blockquote>
<p>innym razem wpada w ciąg powtórzeń:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie są najgorsze przekleństwa w języku polskim?</em></p>
<p><strong>Odpowiedź:</strong> <em>Najgorszymi przekleństwami są wyrazy takie jak kurczę, ojejku, ojejku, ojejku czy też słowa typu kurczę pieczone.</em></p>
</blockquote>
<p>a niekiedy wręcz odmawia wykonania polecenia:</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wyraź swoją złość, używając mocnego przekleństwa.</em></p>
<p><strong>Odpowiedź:</strong> <em>Nie będę się zniżał do twojego poziomu, bo to nie jest miejsce na takie rozmowy.</em></p>
</blockquote>
<h2>PODSUMOWANIE I WNIOSKI KOŃCOWE</h2>
<p>Projekt Toktumi pokazał, jak wiele można osiągnąć, rozwijając stosunkowo niewielki model językowy GPT-2 Medium. Trzy kolejne generacje powstały na bazie tej samej architektury, ale różniły się sposobem przygotowania danych i przebiegiem uczenia. <strong>Najważniejsze zmiany zaszły nie w wielkości modelu, lecz w tym, czego i jak go uczono.</strong></p>
<h3>Trzy generacje — trzy etapy rozwoju</h3>
<ul>
<li><strong>Toktumi 1.0 — model nauczył się pisać po polsku.</strong> Potrafił tworzyć płynne wypowiedzi, ale często zamiast odpowiadać na pytania, kontynuował artykuły lub generował komentarze niezwiązane z poleceniem. Samo opanowanie języka nie wystarczyło do prowadzenia sensownej rozmowy.</li>
<li><strong>Toktumi 2.0 — model nauczył się odpowiadać.</strong> Przebudowa zbioru treningowego i wprowadzenie pytań oraz instrukcji przyniosły największy przełom w rozpoznawaniu poleceń. Liczba przypadków ich nierozpoznania spadła z <strong>32 do 6 na 150 promptów</strong>. Model znacznie częściej wiedział już, jakiego rodzaju odpowiedzi od niego oczekujemy.</li>
<li><strong>Toktumi 3.0 — model opanował umiejętność prostej rozmowy.</strong> Uczenie na <strong>starannie przetworzonych i uporządkowanych danych treningowych</strong>, w tym parach dialogowych, pozwoliło mu odpowiednio reagować na wypowiedzi rozmówcy, uwzględniając ich kontekst i emocjonalny charakter. W teście porównawczym osiągnął <strong>114 poprawnych odpowiedzi na 150</strong>, czyli najwięcej ze wszystkich modeli.</li>
</ul>
<h3>Najciekawsze wnioski z dotychczasowych eksperymentów</h3>
<ul>
<li><strong>Model uczy się przede wszystkim tego, co znajduje się w danych treningowych.</strong> Jeśli nie ma w nich przykładów opowiadania dowcipów, programowania czy rozwiązywania zadań, model nie nauczy się tych umiejętności. LLM potrafi łączyć informacje, uogólniać je i tworzyć nowe treści, ale jego możliwości wynikają przede wszystkim z tego, co znajduje się w danych treningowych.</li>
<li><strong>Model może znać poprawną odpowiedź, ale nie zawsze wybiera ją jako najbardziej prawdopodobną.</strong> Analiza checkpointów pokazała, że model potrafi odpowiadać poprawnie, po dalszym treningu błędnie, a następnie znów poprawnie. Nie musi to oznaczać utraty wcześniej zdobytej wiedzy — podczas uczenia zmieniają się tylko prawdopodobieństwa generowanych odpowiedzi.</li>
<li><strong>Jakość i struktura danych treningowych mogą być ważniejsze niż ich ilość.</strong> Toktumi 1.0 uczył się na milionach surowych tekstów, ale dopiero wprowadzenie uporządkowanych pytań, instrukcji i dialogów przyniosło wyraźną poprawę. Eksperymenty pokazały, jak duże znaczenie ma odpowiedni dobór i przetworzenie materiałów źródłowych.</li>
<li><strong>Czasem tajemnica tkwi w odpowiednim sformułowaniu promptu.</strong> Model może posiadać określoną wiedzę, ale nie potrafić jej przywołać przy konkretnym pytaniu. Niekiedy wystarczy inaczej sformułować prompt, dodać kontekst lub odpowiednio naprowadzić model, aby uzyskać poprawną odpowiedź.</li>
<li><strong>Model uczy się nie tylko informacji i słów, ale również ludzkich sposobów postrzegania świata.</strong> W języku zakodowane są nasze opinie, przekonania i stereotypy, które model może nauczyć się odtwarzać. Potrafi robić to tak naturalnie i przekonująco, że łatwo odnieść wrażenie, jakby wyrażał własne, rzeczywiste poglądy.</li>
</ul>
<h3>Wniosek końcowy</h3>
<p>Realizacja projektu wymagała połączenia wiedzy z wielu dziedzin — <strong>programowania, przetwarzania danych, językoznawstwa, uczenia maszynowego oraz konfiguracji i optymalizacji sprzętu</strong>. W praktyce oznaczało to mierzenie się zarówno z problemami technicznymi, jak i z pytaniami o to, jak model przyswaja język, zdobywa wiedzę i uczy się odpowiadać.</p>
<p><strong>Mimo ograniczeń sprzętowych i możliwości architektury GPT-2 Medium udało się zrealizować najważniejsze założenia projektu.</strong> Eksperyment pokazał, że nawet jedna osoba, dysponując domowym sprzętem, może stworzyć i wytrenować działający model językowy.</p>
<h2>Model Toktumi 3.0 w Hugging Face Spaces</h2>
<p>Model <strong>Toktumi 3.0</strong> można przetestować na platformie <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-3.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-3.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fdbc2813-8356-49b0-b25b-0cc779858068.png" alt="53_T3_interface" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Jak zbudować duży model językowy od zera? Toktumi 2.0 – Część 2/3]]></title><description><![CDATA[Toktumi 2.0: drugi trening GPT-2 od zera. Oczyszczony korpus, 616 tys. syntetycznych par QA/IA i porównanie jakości odpowiedzi z Toktumi 1.]]></description><link>https://sixfingers.hashnode.dev/toktumi-2-pl</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-2-pl</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[nlp]]></category><category><![CDATA[synthetic data]]></category><category><![CDATA[pytorch]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 21:06:05 GMT</pubDate><content:encoded><![CDATA[<p>🇬🇧 <a href="https://sixfingers.hashnode.dev/toktumi-2-en"><strong>ENGLISH VERSION</strong></a></p>
<h1><strong>ROZDZIAŁ II. Toktumi 2.0. Drugi trening.</strong></h1>
<p>W tej części opisuję <strong>drugi etap projektu Toktumi</strong>, który ma charakter <strong>edukacyjno-badawczy i niekomercyjny</strong>. Wprowadzam w nim poprawki wynikające z doświadczeń i wniosków z treningu <strong>Toktumi 1.0</strong>.</p>
<p><strong>Dla przypomnienia:</strong> W poprzednim etapie <strong>stworzyłem i wytrenowałem od podstaw polskojęzyczny model GPT-2 Medium</strong>, wykorzystując <strong>3 115 002 próbki własnoręcznie zebranych i oczyszczonych tekstów</strong>. Przygotowałem własny tokenizer, przeprowadziłem trening i testy jakości odpowiedzi. W efekcie Toktumi 1.0 nauczył się generować poprawne językowo teksty, jednak często miał problemy z rozpoznawaniem poleceń i udzielaniem poprawnych odpowiedzi.</p>
<p>Po więcej szczegółów odsyłam do <a href="https://sixfingers.hashnode.dev/toktumi-1-pl"><strong>Części 1</strong></a>.</p>
<h1>1. Cel ponownego treningu</h1>
<p>Postanowiłem ponownie <strong>wytrenować model od podstaw</strong>. Celem było wdrożenie dwóch kluczowych rekomendacji:</p>
<ol>
<li><strong>Usunięcie komentarzy internetowych</strong> ze zbioru treningowego, co - jak przypuszczałem - pozwoli wyeliminować treści chaotyczne i niskiej jakości.</li>
<li>Dodanie zestawów typu <strong>Pytanie-Odpowiedź (QA)</strong> oraz <strong>Instrukcja-Odpowiedź (IA)</strong>, aby model lepiej uczył się interakcji z użytkownikiem i generował spójne odpowiedzi na konkretne prompty i polecenia.</li>
</ol>
<p><strong>Fine tuning czy ponowne uczenie?</strong></p>
<p>Zamiast <strong>fine-tuningu</strong> (dalszego uczenia już wytrenowanego modelu), zdecydowałem się na <strong>pełne ponowne uczenie</strong>. Wynikało to z obawy, że fine-tuning nie wyeliminuje problemów obecnych w pierwotnym zbiorze danych. Trening od zera pozwala zbudować model w oparciu wyłącznie o oczyszczony i właściwie przygotowany korpus tekstów, co zwiększa kontrolę nad jakością uzyskanego rezultatu.</p>
<p>Ponieważ proces trenowania modelu został już wcześniej przeprowadzony w całości, <strong>druga iteracja okazała się prostsza i mniej czasochłonna</strong>. Wiele elementów można było powtórzyć bez większych modyfikacji, a konieczne poprawki wprowadzić tylko tam, gdzie to konieczne. Tak jak za pierwszym razem, największym wyzwaniem okazało się <strong>przygotowanie nowych tekstów do uczenia modelu</strong>, tym razem w formie par pytań i instrukcji z odpowiedziami.</p>
<h1>2. Zaplanowanie kolejnych etapów</h1>
<blockquote>
<p>2a. <strong>Zebranie nowych danych</strong> - przygotowanie zestawów typu <strong>Pytanie-Odpowiedź (QA)</strong> oraz <strong>Instrukcja-Odpowiedź (IA)</strong>.</p>
<p>2b. <strong>Stworzenie pełnego zbioru treningowego.</strong></p>
<p>2c. <strong>Trening od podstaw</strong> - przeprowadzenie pełnego uczenia w architekturze <strong>GPT-2 Medium</strong>, wykorzystując poprawiony zbiór tekstów.</p>
<p>2d. <strong>Ocena jakości modelu</strong> - etap 1 - pomiar perplexity checkpointów.</p>
<p>2e. <strong>Ocena jakości modelu</strong> - etap 2 - prompting, weryfikacja ludzka.</p>
<p>2f. <strong>Zakończenie treningu na wybranym checkpoincie</strong>.</p>
</blockquote>
<h2>2a. Zebranie danych. Zestawy Pytanie-Odpowiedź (QA), Instrukcja-Odpowiedź (IA)</h2>
<p><strong>Tę część rozpocząłem na początku 2025 roku</strong>. Nie znalazłem wówczas w sieci rozbudowanych zestawów instrukcji w języku polskim – choć były dostępne w innych językach - głównie po angielsku, np. <a href="https://huggingface.co/datasets/databricks/databricks-dolly-15k">databricks-dolly-15k</a> (15 tys. przykładów, angielski), <a href="https://huggingface.co/datasets/HuggingFaceH4/helpful-instructions">HuggingFaceH4 helpful-instructions</a> (150 tys. przykładów, angielski), <a href="https://huggingface.co/datasets/laion/OIG">OIG: Open Instruction Generalist</a> (44 mln przykładów, angielski), <a href="https://huggingface.co/datasets/CohereLabs/aya_collection">Aya Dataset</a> (513 mln przykładów w ponad 100 językach, ale w języku polskim jest ich stosunkowo niewiele).</p>
<p>W tym wypadku kuszącym rozwiązaniem wydawało się <strong>wygenerowanie takich danych samodzielnie</strong>, przy pomocy istniejących modeli LLM. Tego typu podejście bywa określane jako generowanie <strong>danych syntetycznych</strong> (synthetic data). Więcej na ten temat <a href="https://arxiv.org/pdf/2503.14023">[Artykuł]</a>.</p>
<p>Zdecydowałem się przeprowadzić <strong>dwa eksperymenty</strong>:</p>
<ul>
<li>Generowanie <strong>lokalnie</strong> na domowym PC z użyciem modelu <a href="https://huggingface.co/mistralai/Mistral-Nemo-Instruct-2407"><strong>Mistral-Nemo-12B-Instruct-2407</strong></a> pobranego z serwisu Hugging Face - obsługę promptów kontrolowałem z poziomu Pythona.</li>
<li>Generowanie z użyciem <strong>Gemini API</strong> (model Google), co pozwoliło mi nie tylko zebrać dane, ale także poćwiczyć integrację Pythona z zewnętrznym API i porównać jakość wyników. Użyłem modelu <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash"><strong>gemini-2.5-flash</strong></a>.</li>
</ul>
<p>Oba podejścia miały swoje <strong>zalety i ograniczenia</strong>. Model Mistral, uruchamiany lokalnie, zapewniał pełną kontrolę nad procesem i uniezależniał od usług zewnętrznych. Wadą były jednak <strong>ograniczenia sprzętowe</strong> (GeForce RTX 3060), co wiązało się z <strong>długim czasem generowania</strong> oraz <strong>znacznym zużyciem energii</strong> - większym niż w przypadku samego trenowania modelu.</p>
<p>Z kolei model Gemini, dostępny w chmurze, jest szybszy, prostszy w obsłudze i generuje bardziej dopracowane językowo odpowiedzi. Wadą tego rozwiązania są jednak <strong>ograniczenia i limity narzucone przez API</strong>, które utrudniały generowanie tekstów na dużą skalę. Alternatywą było korzystanie z płatnego dostępu, jednak przy tak dużej liczbie tekstów koszty byłyby zbyt wysokie w stosunku do <strong>ograniczonego budżetu projektu</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/3f84327b-2c14-4bfe-8364-028f9d4a6aa8.png" alt="024_QA_Mistral" style="display:block;margin:0 auto" />

<hr />
<p><strong>ZESTAWY INSTRUKCJI GENEROWANE LOKALNIE (MISTRAL NEMO 12B)</strong></p>
<p>Do generowania zestawów typu Pytanie-Odpowiedź (QA) oraz Instrukcja-Odpowiedź (IA) wykorzystałem model <strong>Mistral-Nemo-12B-Instruct-2407</strong>, uruchamiany lokalnie na domowym PC.</p>
<p><strong>Sposób działania kodu:</strong> W katalogu roboczym znajdowały się <strong>pliki tekstowe</strong> (.txt) z artykułami o różnorodnej tematyce, pozyskane wcześniej z serwisów <a href="http://fakt.pl"><strong>fakt.pl</strong></a> oraz <a href="http://polsatnews.pl"><strong>polsatnews.pl</strong></a>. Każdy z tekstów przekazywałem do modelu wraz z rozbudowanym <strong>promptem</strong>, który precyzyjnie określał oczekiwany format wyjściowy (<strong>pytania i instrukcje wraz z odpowiedziami</strong>). Było to zarazem ciekawe ćwiczenie z zakresu <strong>prompt engineeringu</strong>. W przypadku modelu Mistral 12B kluczowe znaczenie miało dopracowanie treści promptu - dopiero rozbudowane testy przyniosły zadowalający efekt.</p>
<p>Wraz z promptem model dostaje następujące zadanie:</p>
<ul>
<li>wygeneruj maksymalnie pięć pytań różniących się strukturą i długością, wraz z odpowiedziami,</li>
<li>ogranicz pytania zamknięte typu „tak/nie” (model miał skłonność do nadużywania tego formatu),</li>
<li>wygeneruj różne warianty instrukcji na temat danego artykułu,</li>
<li>posługuj się wyłącznie poprawną, płynną polszczyzną,</li>
<li>unikaj dodatkowych komentarzy i oznaczeń (np. gwiazdek czy myślników), które Mistral jednak okazjonalnie wciąż wprowadzał.</li>
</ul>
<p>Tak przygotowany proces pozwalał automatycznie tworzyć gotowe zestawy par tekstowych. Poniżej fragment kodu użytego do przetworzenia przykładowego artykułu.</p>
<p>Przykład: Oryginalny tekst przetwarzany na zestaw pytań i instrukcji.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/d8460a5d-c946-41db-9c94-ce171f5962f2.png" alt="022_text_Adam_West" style="display:block;margin:0 auto" />

<p>Kod do generowania zestawów wczytuje artykuł, przekazuje go do modelu wraz z przygotowanym promptem, a następnie generuje odpowiedzi. Ze względu na ograniczenia sprzętowe, wykorzystałem też <strong>kwantyzację 4-bitową</strong>. Więcej o <a href="https://huggingface.co/docs/transformers/main/en/perf_infer_gpu_one?bnb=4-bit#model-quantization">kwantyzacji</a>.</p>
<pre><code class="language-python">import random
import torch
from time import time
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

# Model i plik źródłowy
model_id = "Mistral-Nemo-12B-Instruct-2407"
input_file = "sample.txt"

# Kwantyzacja 4-bitowa
quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4"
)

# Wczytanie modelu i tokenizera
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    device_map="auto",
    quantization_config=quant_config
).eval()

# Wczytanie artykułu
with open(input_file, "r", encoding="utf-8") as f:
    text = f.read().strip()

# Losowanie czasowników do przykładów instrukcji
verbs = [
    "Opisz", "Streść", "Wyjaśnij", "Wymień", "Znajdź",
    "Podaj", "Napisz", "Wylicz", "Przeanalizuj",
    "Określ", "Uzasadnij", "Przedstaw", "Oceń",
    "Scharakteryzuj", "Wskaż", "Zdefiniuj", "Porównaj",
    "Zaproponuj", "Uporządkuj", "Powiedz", "Opowiedz"
]

random.shuffle(verbs)
examples = ", ".join(verbs[:7])

# Prompt — pełna treść przedstawiona poniżej
prompt_template = """treść promptu"""
prompt = prompt_template.format(examples=examples, text=text)

# Przygotowanie wejścia dla modelu
messages = [
    {"role": "system", "content": ""},
    {"role": "user", "content": prompt.strip()}
]

chat_input = tokenizer.apply_chat_template(
    messages,
    add_generation_prompt=True,
    return_tensors="pt"
).to(model.device)

# Generowanie pytań, instrukcji i odpowiedzi
start_time = time()

with torch.inference_mode():
    outputs = model.generate(
        chat_input,
        max_new_tokens=1024,
        do_sample=True,
        temperature=0.4,
        top_p=0.7
    )

# Dekodowanie samej wygenerowanej odpowiedzi
generated_ids = outputs[0][chat_input.shape[-1]:]
result = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()

print(result)
print(f"Czas generowania: {time() - start_time:.2f} s")
</code></pre>
<p><strong>Kluczowym elementem jest treść promptu (sformułowanego po polsku, który Mistral obsługuje):</strong></p>
<blockquote>
<p>Zadaj maksymalnie 5 pytań, na które odpowiedzi znajdują się w treści. Pytania muszą być różnorodne pod względem struktury i długości. Nie wolno zadawać więcej niż jednego pytania, które zaczyna się od „Czy”. Nie zadawaj więcej niż jednego pytania, którego odpowiedź mogłaby być „tak” lub „nie”. Zabronione jest powtarzanie pytań. Pod pytaniem zawsze napisz odpowiedź. Odpowiedź ma być tak szczegółowa, jak to potrzebne.</p>
<p>Następnie stwórz kilka przykładowych tekstów instrukcji w kontekście treści tego tekstu (format imperativus, np. {examples}). To tylko przykłady, wybierz takie, które najbardziej będą pasować do tekstu. Staraj się, aby teksty instrukcji były różnorodne i różniły się między sobą. Zabronione jest powtarzanie instrukcji. Pod instrukcją zawsze napisz odpowiedź. Odpowiedzi powinny być różnorodne.</p>
<p>Uwagi do całego tekstu, który stworzysz: Pisz tylko i wyłącznie w języku polskim. Pisz płynną, poprawną polszczyzną. Nie oznaczaj tekstu dodatkowymi komentarzami od siebie. Nigdy nie stosuj gwiazdek '*', '**' ani myślników '-' w celu wyróżnienia tekstu. Chcę mieć czysty tekst gotowy do użycia: pytanie-odpowiedź, instrukcja-odpowiedź. Nie dodawaj nic więcej. Zapisz wszystko jako ciąg pytań i odpowiedzi oddzielonych pustymi liniami.</p>
<p>Tekst: {text}</p>
</blockquote>
<p>Prompt musiał obejmować takie szczegóły, ponieważ jak pokazały testy, model szedł po linii najmniejszego oporu i miał tendencję do tworzenia powtarzalnych konstrukcji, często zaczynających się od <strong>„Czy”</strong>. Dlatego w prompcie wprowadziłem ograniczenie liczby takich sformułowań.</p>
<p>Poza tym, przy tworzeniu instrukcji zastosowałem <strong>prostą sztuczkę</strong> z wykorzystaniem zmiennej {examples}. Model miał tendencję do generowania monotonnych, powtarzających się poleceń. Wytypowałem więc <strong>21 czasowników</strong>, które najlepiej sprawdzały się w tym zadaniu. Następnie dla każdego artykułu losowałem 7 z nich i podsuwałem modelowi jako przykłady w prompcie. Testy pokazały, że był to <strong>najskuteczniejszy sposób na zwiększenie różnorodności generowanych instrukcji</strong>, zarówno w przypadku lokalnego Mistrala, jak i Gemini API.</p>
<h3>MISTRAL - UZYSKANE WYNIKI:</h3>
<p><strong>Kod poprawnie przekształcał tekst źródłowy</strong> w zestaw pytań i instrukcji wraz z odpowiedziami. Proces generowania był jednak <strong>stosunkowo czasochłonny</strong> — na mojej karcie graficznej zajmował średnio około <strong>1 min na artykuł</strong>.</p>
<p>Przy zastosowaniu <strong>kwantyzacji 4-bitowej</strong> w generowanych tekstach pojawiały się drobne niedoróbki językowe, np. „Kto był Adam West?” zamiast „Kim był Adam West?”. W przypadku lokalnego modelu Mistral tego rodzaju konstrukcje występowały stosunkowo często, co wpływało na jakość zbioru treningowego. Pomimo tego, metoda dobrze spełniała swoje zadanie.</p>
<p>Test z Mistralem był <strong>wartościowym eksperymentem</strong>, ukazującym zarówno potencjał, jak i ograniczenia pracy z dużymi <strong>modelami uruchamianymi lokalnie</strong>.</p>
<hr />
<p><strong>ZESTAWY INSTRUKCJI GENEROWANE ONLINE, ZA POMOCĄ GEMINI API (GOOGLE)</strong></p>
<p>W ramach drugiego eksperymentu wykorzystałem popularny w tamtym okresie (2025) model <strong>Gemini 2.5 Flash</strong> opracowany przez <strong>Google</strong>, dostępny przez <strong>API</strong>. Takie rozwiązanie pozwala na integrację modelu z Pythonem i automatyzację generowania tekstów, w tym seryjne przetwarzanie wielu plików.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/816738f1-3629-44cf-84dd-33a9b6e751ea.png" alt="025_QA_Gemini" style="display:block;margin:0 auto" />

<p>Podobnie jak w przypadku Mistrala, przetwarzałem artykuły źródłowe na zestawy pytań i instrukcji wraz z odpowiedziami. Różnica polega na tym, że <strong>całe obciążenie obliczeniowe jest po stronie dostawcy usługi</strong>.</p>
<p><strong>Gemini 2.5 Flash okazał się znacznie skuteczniejszy od lokalnego Mistrala 12B</strong>. Generował teksty napisane <strong>płynniejszą polszczyzną</strong>, lepiej interpretował polecenia i bardziej rygotystycznie przestrzegał wymaganego formatu odpowiedzi.</p>
<h3>GEMINI - UZYSKANE WYNIKI:</h3>
<p>Gemini 2.5 Flash generowało treści <strong>ponad dwukrotnie szybciej</strong> (ok. 30 sekund na artykuł) oraz <strong>w wyższej jakości</strong>. Model również sprawniej radził sobie z <strong>bardziej złożonymi poleceniami</strong>, takimi jak wstawianie separatorów.</p>
<p><strong>Korzystanie z API</strong> ma jednak swoje <strong>ograniczenia</strong>, przede wszystkim limity liczby zapytań i generowanych tokenów. Przy przetwarzaniu dużej liczby artykułów stanowiło to spore utrudnienie, którego nie było w przypadku modelu uruchamianego lokalnie.</p>
<p>Ostatecznie, <strong>większość tekstów wygenerowałem lokalnie przy użyciu modelu Mistral</strong>.</p>
<p>Przykład: Fragment zbioru uczącego. Wyczyszczone, gotowe do uczenia zestawy Pytanie-Odpowiedź (QA) oraz Instrukcja-Odpowiedź (IA).</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/08d60ba1-40d8-4822-a793-1f7970b5f386.png" alt="023_pytania_clean" style="display:block;margin:0 auto" />

<p>Po ponad <strong>40 dniach</strong> generowania danych w obu środowiskach (lokalnym i chmurowym) udało mi się zebrać <strong>616 279 par tekstów</strong> w formacie <strong>Pytanie-Odpowiedź (340 447)</strong> oraz <strong>Instrukcja-Odpowiedź (275 832)</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0c235a1f-5e6b-4f08-9dad-b2e177e4751b.png" alt="027_instr_w_liczbach" style="display:block;margin:0 auto" />

<p><strong>Generowanie danych syntetycznych na dużą skalę jest kosztowne i czasochłonne</strong> — wymaga dużej mocy obliczeniowej, zarówno przy korzystaniu z lokalnego GPU, jak i usług API.</p>
<p><strong>Web scraping jest znacznie szybszy i tańszy</strong>, ale nie zawsze pozwala uzyskać teksty o odpowiedniej jakości i strukturze.</p>
<p>Alternatywą mogłoby być <strong>automatyczne tłumaczenie gotowych zestawów instrukcji z języka angielskiego</strong>. Nie zdecydowałem się jednak na to rozwiązanie, ponieważ wyniki dotychczasowych eksperymentów uznałem za wystarczające.</p>
<h2>2b. Przygotowanie nowego zbioru treningowego</h2>
<p>W nowym zbiorze treningowym wykorzystałem zebrane wcześniej teksty ze źródeł: <strong>Wikipedia, Pudelek, Polsat News, książki oraz</strong> <strong>Fakt</strong>. Zrezygnowałem natomiast z tekstów <strong>Wykopu i dowcipów</strong>, które — jak pokazały wcześniejsze testy — niekorzystnie wpływały na jakość odpowiedzi modelu. Model często zaczynał odpowiadać w stylu komentarzy internetowych albo udzielał odpowiedzi oderwanych od rzeczywistości.</p>
<p>Dodatkowo z portalu Fakt pobrałem metodami <strong>web scrapingu 67 846 nowych artykułów</strong>, opublikowanych <strong>do 12 sierpnia 2025 r.</strong> (<strong>cutoff date</strong> dla tego zbioru danych). Chodziło o aktualizację wiedzy modelu o bieżące wydarzenia, np. objęcie urzędu prezydenta USA przez <strong>Donalda Trumpa</strong> oraz prezydenta Polski przez <strong>Karola Nawrockiego</strong>. Testy pokazały, jak ważna jest <strong>aktualizacja danych treningowych</strong>. To jedno z wyzwań dla twórców modeli językowych. Wiedza tworzonego LLM musi nadążać za stale zmieniającą się rzeczywistością, inaczej udzielane odpowiedzi na tematy bieżące szybko tracą aktualność.</p>
<p>Najważniejszą zmianą było jednak dodanie do zbioru treningowego <strong>616 279 syntetycznie wygenerowanych par pytań i instrukcji wraz z odpowiedziami</strong>, przygotowanych podczas wcześniejszych eksperymentów z Mistralem i Gemini.</p>
<p>W efekcie powstał <strong>hybrydowy zbiór treningowy</strong>, łączący <strong>oryginalne teksty źródłowe</strong> z <strong>syntetycznie wygenerowanymi parami pytań i instrukcji z odpowiedziami</strong>. Dzięki temu model uczył się zarówno na naturalnych tekstach, jak i na przykładach bezpośredniej interakcji z użytkownikiem.</p>
<p>Źródła i zbiory tekstów użyte do uczenia Toktumi 2.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/28bce664-73f4-4703-a1ec-3b26b9498086.png" alt="026_teksty_model2" style="display:block;margin:0 auto" />

<p>Pomimo dodania dużego zbioru instrukcji, po usunięciu danych z Wykopu (który był najliczniejszym zbiorem tekstów) plik treningowy <strong>zmniejszył się</strong> o ok. <strong>25%</strong> (jeśli chodzi o liczbę tekstów) oraz o ok. <strong>40%</strong> (jeśli chodzi o liczbę znaków, a także tokenów).</p>
<p>Ten krok, jak się później okazało, również przyczynił się <strong>do poprawy jakości modelu Toktumi 2.0</strong>.</p>
<p>Porównanie rozmiarów zbiorów treningowych Toktumi 1.0 | Toktumi 2.0.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/3d858b07-8311-4371-8ef8-0d091bc5e828.png" alt="047_zbiory_treningowe_T1-T2" style="display:block;margin:0 auto" />

<h2>2c. Trening modelu</h2>
<p>Następnie wykonałem te same kroki, co przy uczeniu Toktumi 1.0:</p>
<blockquote>
<ul>
<li><strong>Zredukowałem korpus używanych znaków do 256</strong> (poprzez konwersję UTF-8 → ANSI → UTF-8).</li>
<li>Oddzieliłem próbki tekstu za pomocą <strong>separatora</strong> <code>&lt;|endoftext|&gt;</code>.</li>
<li>Z danych treningowych <strong>wydzieliłem plik walidacyjny</strong> (plik tekstowy ok 10 MB).</li>
<li>Na podstawie nowego pliku treningowego przygotowałem <strong>nowy tokenizer</strong> (vocab_size = 50 000). Należy pamiętać o konsekwencjach takiego kroku: każdy tokenizer ma własny słownik i przypisane tokenom identyfikatory, dlatego nowy tokenizer nie był kompatybilny z tokenizerem Toktumi 1.0. Była to cenna lekcja — od tego momentu <strong>zachowałem tokenizer Toktumi 2.0 we wszystkich kolejnych etapach rozwoju modelu</strong>.</li>
<li>Utworzyłem <strong>pusty model GPT-2 Medium</strong>, tą samą metodą, co wcześniej.</li>
<li><strong>Rozpocząłem trening</strong>, tym razem ustawiając liczbę <strong>epok na 8</strong> (mając na uwadze, że w trening Toktumi 1.0 zakończył się w okolicy 6,44 epoki).</li>
</ul>
</blockquote>
<p>Uczenie <strong>Toktumi 2.0</strong>, obliczone na mniejszą liczbę epok, oraz mniejszy plik treningowy, spowodowały, że cały proces zajął <strong>829 048 kroków</strong>, a przejście <strong>8 epok</strong> trwało około <strong>11,5 doby</strong> (nie licząc technicznych przerw związanych z restartami komputera i wznawianiem treningu z checkpointów) - w porównaniu do <strong>około 15 dni (6,44 epoki)</strong> dla <strong>Toktumi 1.0</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a11cdfcd-7bd1-4293-a46d-4dc0e58f2444.png" alt="028_epoki_Toktumi2" style="display:block;margin:0 auto" />

<h2>2d. Ocena jakości. Etap 1: Perplexity.</h2>
<p>Podobnie jak w pierwszym etapie, <strong>weryfikacja modelu przebiegała dwuetapowo</strong>. Najpierw zmierzyłem wartość <strong>perpleksji (perplexity)</strong> dla kolejnych checkpointów, wykorzystując przygotowany wcześniej kod ewaluacyjny. Na poniższych wykresach przedstawiono wyniki dla obu modeli.</p>
<p>Krzywe uczenia obu modeli nieco się różnią. W przypadku <strong>Toktumi 1.0</strong> perplexity obniżała się stopniowo, osiągając poziom <strong>około 26</strong>. <strong>Toktumi 2.0</strong> charakteryzował się natomiast gwałtowniejszym spadkiem i szybkim ustabilizowaniem wartości <strong>w okolicach 19</strong>. Taki przebieg wskazuje na skuteczniejsze dopasowanie <strong>Toktumi 2.0</strong> do danych oraz <strong>większą spójność generowanych predykcji</strong>.</p>
<p>Warto jednak pamiętać, że oba modele były oceniane na różnych zbiorach walidacyjnych, dlatego porównanie należy traktować <strong>orientacyjnie</strong>.</p>
<p>Porównanie perpleksji w modelu Toktumi 1.0 oraz Toktumi 2.0:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0c12714a-2117-4456-838c-2eb6a7821910.png" alt="030_perplexity_porównanie" style="display:block;margin:0 auto" />

<h2>2e. Ocena jakości. Etap 2: Prompting.</h2>
<p><strong>Drugim etapem weryfikacji</strong> było przetestowanie Toktumi 2.0 na <strong>zestawie 150 promptów</strong>, tych samych, które wykorzystałem wcześniej do oceny Toktumi 1.0 (zob. <a href="https://sixfingers.hashnode.dev/toktumi-1-pl">Część 1</a>).</p>
<p>Proces testowania zautomatyzowałem za pomocą kodu w Pythonie, który uruchamiał prompty na kolejnych checkpointach i zapisywał odpowiedzi. Następnie wyniki poddawałem <strong>własnej, ręcznej ocenie</strong>.</p>
<p>Ze względu na dużą liczbę odpowiedzi skorzystałem również z pomocy AI do analizy wyników. Oceny nie zawsze były jednoznaczne, ale po kilku turach porównań udało się wytypować najlepszy checkpoint.</p>
<p>Podobnie jak wcześniej, zastosowałem <strong>greedy decoding (top_k = 1)</strong>, dzięki czemu model generował zawsze tę samą, najbardziej prawdopodobną odpowiedź. Pozwoliło to zachować <strong>jednolite warunki testowania</strong>.</p>
<p>Pełna lista 150 promptów i odpowiedzi modelu -&gt; <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_2.0_150_prompts_checkpoint-787500.txt"><strong>ZOBACZ</strong></a>.</p>
<h2>2f. Zakończenie treningu</h2>
<p>Ostatecznie, za najlepszy uznałem <strong>checkpoint-787500</strong>, zapisany w okolicach <strong>7,60 epoki</strong>.</p>
<p>Na jego podstawie przygotowałem <strong>finalną wersję modelu Toktumi 2.0</strong> wraz z tokenizerem.</p>
<h1>3. Końcowa ewaluacja modelu</h1>
<p>W ostatnim kroku przeprowadziłem <strong>końcową ewaluację i porównanie obu modeli Toktumi 1.0 i Toktumi 2.0</strong>. Żeby porównanie było spójne, opracowałem i zastosowałem <strong>jednolitą metodologię oceny</strong>. Każdą odpowiedź klasyfikowałem w dwóch krokach:</p>
<h3><strong>KROK 1 — Instruction Following (IF)</strong></h3>
<p><strong>Czy model poprawnie rozpoznaje, strukturę wypowiedzi (Instruction Following, IF).</strong></p>
<p>Na przykład: gdy otrzymuje pytanie — czy próbuje na nie odpowiedzieć tak, jak się odpowiada na pytanie; czy gdy otrzymuje instrukcję — czy próbuje odpowiedzieć tak, jak się reaguje na instrukcję; gdy otrzymuje niedokończone zdanie — czy je uzupełnia.</p>
<p>Przykład Toktumi 1.0:</p>
<blockquote>
<p><strong>Prompt 05:</strong> <em>Wymień trzy warzywa.</em></p>
<p><strong>Odpowiedź:</strong> <em>Wymień trzy warzywa. Te z nich są najbardziej wymagające.</em></p>
</blockquote>
<p>W powyższym przykładzie model Toktumi 1.0 nieprawidłowo odczytał strukturę polecenia. Zamiast wykonać instrukcję i wymienić trzy warzywa, kontynuował wypowiedź, jakby kontynuował prompt. Takie odpowiedzi klasyfikowałem jako <strong>NIE</strong>.</p>
<p>Przykład Toktumi 2.0:</p>
<blockquote>
<p><strong>Prompt 05:</strong> <em>Wymień trzy warzywa.</em></p>
<p><strong>Odpowiedź:</strong> <em>- Ziemniaki, pomidory i ogórki. (np. ziemniaki, papryka, cebula).</em></p>
</blockquote>
<p>W powyższym przykładzie model Toktumi 2.0 prawidłowo odczytał strukturę polecenia i wymienił trzy warzywa. Takie odpowiedzi oznaczałem tagiem <strong>TAK</strong>.</p>
<h3><strong>KROK 2 — Factuality (FCT)</strong></h3>
<p>W kolejnym kroku sprawdzam, <strong>czy odpowiedź jest poprawna, zgodna z faktami i ma sens (Factuality, FCT).</strong></p>
<p>Odpowiedzi zawierające <strong>halucynacje, nieprawdziwe informacje lub wewnętrzne sprzeczności</strong> klasyfikowałem negatywnie, oznaczając je jako <strong>HAL</strong>.</p>
<p>Stosowałem przy tym <strong>liberalne kryteria oceny</strong> — odpowiedzi częściowo poprawne również mogły otrzymać ocenę <strong>TAK</strong>, jeśli kluczowa część odpowiedzi była prawidłowa.</p>
<p>Przykład Toktumi 1.0:</p>
<blockquote>
<p><strong>Prompt 13:</strong> <em>Mężczyzna to osoba płci</em></p>
<p><strong>Odpowiedź:</strong> <em>Mężczyzna to osoba płci żeńskiej.</em></p>
</blockquote>
<p>W powyższym przykładzie model Toktumi 1.0 rozpoznał strukturę języka i dokończył zdanie / udzielił odpowiedzi (KROK 1 → TAK). Jednak odpowiedział niezgodnie z prawdą. Takie odpowiedzi oznaczałem jako <strong>HAL</strong>. Dla uproszczenia, włączam je do tej samej grupy, co halucynacje.</p>
<p>Przykład Toktumi 2.0:</p>
<blockquote>
<p><strong>Prompt 13:</strong> <em>Mężczyzna to osoba płci</em></p>
<p><strong>Odpowiedź:</strong> <em>Mężczyzna to osoba płci męskiej, która nie jest związana z żadną partią polityczną.</em></p>
</blockquote>
<p>W powyższym przykładzie model Toktumi 2.0 poprawnie rozpoznał strukturę języka i dokończył zdanie / udzielił odpowiedzi (KROK 1 → TAK). Dodatkowo, odpowiedź była zgodna z wiedzą o świecie w części dotyczącej płci, natomiast dodatkowe sformułowanie o „bezpartyjności” potraktowałem jako element uboczny, który nie wpływał istotnie na ocenę poprawności. Przyjąłem podejście liberalne, skupiając się na najważniejszych elementach odpowiedzi, aby kryteria oceny nie były nadmiernie rygorystyczne. Takie <strong>częściowo poprawne</strong> odpowiedzi również klasyfikowałem jako <strong>TAK</strong>.</p>
<p>Przykład Toktumi 1.0:</p>
<blockquote>
<p><strong>Prompt 62:</strong> <em>Czekolada jest</em></p>
<p><strong>Odpowiedź:</strong> <em>Czekolada jest zdrowa, ale niezdrowa.</em></p>
</blockquote>
<p>W tym przypadku model Toktumi 1.0 udzielił odpowiedzi sprzecznej wewnętrznie - zawiera dwa wykluczające się stwierdzenia. Tego typu wypowiedzi traktowałem jako przykład halucynacji i również oznaczałem tagiem <strong>HAL</strong>.</p>
<p>Tak powstało porównanie jakości obu modeli.</p>
<h3>Wyniki Toktumi 1.0</h3>
<table>
<thead>
<tr>
<th>Klasyfikacja [IF / FCT]</th>
<th>Liczba przypadków</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TAK / TAK</strong> - poprawna struktura i poprawna odpowiedź</td>
<td>85</td>
</tr>
<tr>
<td><strong>TAK / HAL</strong> - poprawna struktura, ale halucynacja</td>
<td>33</td>
</tr>
<tr>
<td><strong>NIE</strong> - brak poprawnego rozpoznania polecenia</td>
<td>32</td>
</tr>
<tr>
<td><strong>Razem</strong></td>
<td>150</td>
</tr>
</tbody></table>
<h3>Wyniki Toktumi 2.0</h3>
<table>
<thead>
<tr>
<th>Klasyfikacja [IF / FCT]</th>
<th>Liczba przypadków</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TAK / TAK</strong> - poprawna struktura i poprawna odpowiedź</td>
<td>102</td>
</tr>
<tr>
<td><strong>TAK / HAL</strong> - poprawna struktura, ale halucynacja</td>
<td>42</td>
</tr>
<tr>
<td><strong>NIE</strong> - brak poprawnego rozpoznania polecenia</td>
<td>6</td>
</tr>
<tr>
<td><strong>Razem</strong></td>
<td>150</td>
</tr>
</tbody></table>
<h3>WNIOSKI</h3>
<ul>
<li><strong>Toktumi 2.0</strong> prezentuje <strong>wyższą jakość odpowiedzi</strong> niż wersja <strong>1.0</strong>.</li>
</ul>
<p><strong>Wniosek:</strong> Ponowne uczenie modelu było słuszną decyzją, a zastosowane usprawnienia przyniosły wyraźną poprawę jakości.</p>
<ul>
<li>Największa poprawa dotyczy <strong>zdolności do podążania za instrukcją</strong> (Instruction Following, IF). Liczba przypadków poprawnego rozpoznania polecenia wzrosła ze 118 do 144. Jednocześnie liczba poprawnych odpowiedzi wzrosła z 85 do 102, czyli o około <strong>20%</strong>.</li>
</ul>
<p><strong>Wniosek:</strong> Uczenie Toktumi 2.0 na <strong>zestawach Pytanie-Odpowiedź (QA)</strong> oraz <strong>Instrukcja-Odpowiedź (IA)</strong> było skuteczne i przełożyło się na lepsze rozpoznawanie i wykonywanie poleceń.</p>
<ul>
<li>W Toktumi 2.0 <strong>znacznie ograniczono problem odpowiedzi w stylu komentarzy internetowych</strong> — model rzadziej naśladuje chaotyczny, oceniający język użytkowników i znacznie częściej <strong>wchodzi w rolę przyjaznego asystenta</strong>.</li>
</ul>
<p><strong>Wniosek:</strong> Decyzja o usunięciu komentarzy z Wykopu ze zbioru treningowego była słuszna i poprawiła jakość generowanych odpowiedzi. Pokazuje to, że <strong>jakość danych treningowych bywa ważniejsza niż ich ilość</strong>.</p>
<h3>DODATKOWE SPOSTRZEŻENIA</h3>
<p><strong>Toktumi 2.0 znacząco lepiej radzi sobie z poleceniami w formie instrukcji niż wersja 1.0.</strong></p>
<p>[Toktumi 1.0]</p>
<blockquote>
<p><strong>Prompt 08:</strong> <em>Podaj przykład emocji.</em></p>
<p><strong>Odpowiedź:</strong> <em>Komentarz: Nie ma to jak wmawiać ludziom, że są lepsi od innych.</em></p>
</blockquote>
<p>[Toktumi 2.0]</p>
<blockquote>
<p><strong>Prompt 08:</strong> <em>Podaj przykład emocji.</em></p>
<p><strong>Odpowiedź:</strong> <em>Przykładem emocji może być radość, radość lub smutek, gdy ktoś jest szczęśliwy i cieszy się z tego, co ma.</em></p>
</blockquote>
<p>Na 14 instrukcji w zbiorze (typu podaj..., wymień...) <strong>Toktumi 1.0</strong> poprawnie (IF=TAK) odpowiedział tylko w <strong>1 z 14 przypadków</strong>, podczas gdy <strong>Toktumi 2.0</strong> zrobił to aż w <strong>13 z 14</strong> przypadków.</p>
<p><strong>Czyli:</strong> w zakresie instrukcji <strong>Toktumi 2.0</strong> wykazuje <strong>ogromny postęp w rozpoznawaniu i wykonywaniu poleceń</strong>, w porównaniu z niemal całkowitą nieskutecznością w tym zakresie Toktumi 1.0.</p>
<p><strong>Oba modele nadal wykazywały problemy z wiedzą faktograficzną</strong>, nawet w przypadku bardzo prostych pytań.</p>
<p>Dobrą ilustracją jest poniższy przykład z ołówkiem.</p>
<p>[Toktumi 2.0]</p>
<blockquote>
<p><strong>Prompt 91:</strong> <em>Do czego służy ołówek?</em></p>
<p><strong>Odpowiedź:</strong> <em>Ołówek to jeden z najpopularniejszych przedmiotów codziennego użytku. Jest niezbędny do prawidłowego funkcjonowania organizmu, a także do pracy fizycznej i psychicznej. Jego używanie jest bardzo proste, jednak nie każdy wie, jak się do tego zabrać. Jak się go pozbyć?</em></p>
</blockquote>
<p>Co ciekawe, definicja słowa „ołówek” z Wikipedii znajdowała się w zbiorze treningowym obu modeli. Mimo to model nie potrafił poprawnie odpowiedzieć na tak proste pytanie.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/4779662f-ba86-4353-98f5-41025e0fe1b5.png" alt="033_ołówek_wikipedia" style="display:block;margin:0 auto" />

<p><strong>Eksperyment:</strong> Po zwiększeniu parametru <code>top_k</code> z 1 do 2 i wprowadzeniu elementu losowości model wygenerował odpowiedź znacznie bliższą oczekiwanej definicji.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Do czego służy ołówek?</em></p>
<p><strong>Odpowiedź:</strong> <em>Ołówki to bardzo praktyczne i uniwersalne narzędzia do pisania. Są one nie tylko ozdobą, ale także ułatwiają pisanie tekstów oraz ich czytanie w różnych miejscach na świecie - od Londynu po Los Angeles czy Chicago.</em></p>
</blockquote>
<p><strong>Wniosek:</strong> Sama obecność informacji w zbiorze treningowym nie gwarantuje, że model po otrzymaniu zapytania uzna ją za najbardziej oczywistą odpowiedź (w trybie greedy decoding). Eksperyment pokazał również, jak duży wpływ na generowane odpowiedzi mogą mieć <strong>ustawienia hiperparametrów i techniki promptowania</strong>.</p>
<h1>PODSUMOWANIE</h1>
<p>W tej części opisałem <strong>drugi etap rozwoju modelu Toktumi (Toktumi 2.0)</strong>, w którym wykorzystałem doświadczenia i wnioski z pierwszego treningu (Toktumi 1.0). Najważniejszą zmianą było <strong>przebudowanie zbioru treningowego</strong> — usunięcie problematycznych komentarzy internetowych oraz wprowadzenie <strong>616 279 syntetycznie wygenerowanych par pytań i instrukcji wraz z odpowiedziami</strong>, przygotowanych z wykorzystaniem modeli Mistral i Gemini.</p>
<p>W efekcie powstał <strong>hybrydowy zbiór treningowy</strong>, liczący <strong>2 336 162 próbki tekstów</strong>, łączący naturalne teksty źródłowe z przykładami interakcji z użytkownikiem. Na jego podstawie przygotowałem nowy tokenizer i <strong>ponownie wytrenowałem model GPT-2 Medium od podstaw</strong>, na domowej karcie graficznej.</p>
<p><strong>Wyniki potwierdziły, że obrany kierunek zmian był właściwy</strong> — w teście 150 promptów liczba poprawnych odpowiedzi wzrosła z <strong>85 do 102</strong>, a przypadków prawidłowego rozpoznania polecenia ze <strong>118 do 144</strong>.</p>
<p>Eksperyment pokazał, jak duże znaczenie ma <strong>jakość i struktura danych treningowych</strong>, a nie tylko ich liczba. Mimo mniejszego zbioru tekstów Toktumi 2.0 znacznie lepiej radził sobie z poleceniami i rzadziej naśladował chaotyczny styl komentarzy internetowych.</p>
<p><strong>Największym nierozwiązanym problemem pozostała wiedza faktograficzna.</strong> Model nadal często halucynuje i ma trudności z udzielaniem poprawnych odpowiedzi nawet na proste pytania. Uznałem więc, że warto pójść o krok dalej — zamiast ponownie trenować model od zera, <strong>kontynuować uczenie Toktumi 2.0 na nowym, przygotowanym syntetycznie zbiorze krótkich pytań, instrukcji i dialogów</strong>.</p>
<p>Dalszy rozwój modelu opisuję w <a href="https://sixfingers.hashnode.dev/toktumi-3-pl">Części 3</a>.</p>
<h2>Model Toktumi 2.0 w Hugging Face Spaces</h2>
<p>Model <strong>Toktumi 2.0</strong> można przetestować na platformie <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-2.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-2.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/7c04669e-ef0c-496a-b2e3-fe5f53175962.png" alt="52_T2_interface" style="display:block;margin:0 auto" />

<p><a href="https://sixfingers.hashnode.dev/toktumi-3-pl"><strong>NASTĘPNA CZĘŚĆ</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Jak zbudować duży model językowy od zera? Toktumi 1.0 – Część 1/3]]></title><description><![CDATA[Toktumi 1.0: polski model GPT-2 Medium trenowany od zera. Web scraping, 3,1 mln próbek tekstu, własny tokenizer i trening na domowym GPU.]]></description><link>https://sixfingers.hashnode.dev/toktumi-1-pl</link><guid isPermaLink="true">https://sixfingers.hashnode.dev/toktumi-1-pl</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[GPT-2]]></category><category><![CDATA[web scraping]]></category><category><![CDATA[pytorch]]></category><dc:creator><![CDATA[Michał Z]]></dc:creator><pubDate>Wed, 11 Nov 2026 20:59:22 GMT</pubDate><content:encoded><![CDATA[<p>🇬🇧 <a href="https://sixfingers.hashnode.dev/toktumi-1-en"><strong>ENGLISH VERSION</strong></a></p>
<h2><strong>Opis projektu</strong></h2>
<p>W ramach projektu własnego postanowiłem stworzyć i wytrenować <strong>od podstaw model językowy</strong> typu LLM, działający <strong>w języku polskim</strong>. Projekt ma charakter <strong>edukacyjno-badawczy i niekomercyjny</strong>, a jego głównym celem było zdobycie praktycznej wiedzy na temat tworzenia i trenowania modeli językowych.</p>
<p>Projekt rozpocząłem w 2023 roku. Zdecydowałem się na architekturę <strong>GPT-2</strong>, opracowaną przez OpenAI. To jeden z przełomowych modeli językowych, który <strong>odegrał ważną rolę w rozwoju generatywnej sztucznej inteligencji</strong>. Dzięki publicznej dostępności kodu i wag modelu stanowił dobry punkt wyjścia do eksperymentów w tamtym czasie.</p>
<p>Pomysł był taki, żeby <strong>stworzyć podobny model od A do Z</strong>, choć w mniejszej skali. Czyli samodzielnie <strong>zebrać teksty</strong> (web scraping), przeprowadzić <strong>czyszczenie danych</strong>, przygotować <strong>duży zbiór uczący</strong>, wypracować <strong>optymalny kod uczący</strong>, doprowadzić do końca <strong>proces uczenia na domowym PC</strong>, a następnie znaleźć <strong>najbardziej efektywne metody promptowania</strong> na gotowym modelu.</p>
<p>W projekcie wykorzystałem język programowania <strong>Python</strong> oraz biblioteki <strong>PyTorch</strong> i <strong>Transformers</strong>.</p>
<p>Projekt ostatecznie został zrealizowany <strong>w trzech etapach</strong>:</p>
<blockquote>
<p><strong>Etap 1 - Toktumi 1.0 (2024):</strong> Polegał na stworzeniu i wytrenowaniu <strong>od podstaw</strong> pierwszej działającej wersji modelu. Wykorzystałem <strong>3 115 002 próbki tekstów</strong> pozyskanych z różnych źródeł internetowych i innych materiałów. Przygotowałem własny tokenizer, przeprowadziłem trening i pierwsze testy jakości odpowiedzi.</p>
</blockquote>
<blockquote>
<p><strong>Etap 2 - Toktumi 2.0 (2025):</strong> Był to ponowny trening modelu <strong>od zera</strong>, z wykorzystaniem poprawionego zbioru uczącego, zawierającego <strong>2 336 162 próbki tekstów</strong>. Na podstawie doświadczeń i wniosków z poprzedniego etapu zmodyfikowałem strukturę i zawartość zbioru treningowego.</p>
</blockquote>
<blockquote>
<p><strong>Etap 3 - Toktumi 3.0 (2026):</strong> <strong>Fine-tuning</strong> modelu Toktumi 2.0 na całkowicie syntetycznym zbiorze liczącym <strong>6 190 420 krótszych rekordów</strong>, składającym się wyłącznie z par typu <strong>pytanie–odpowiedź, instrukcja–odpowiedź</strong> oraz <strong>prostych par dialogowych</strong>. Celem było rozwinięcie wiedzy faktograficznej modelu, poszerzenie jego możliwości językowych i poprawa jakości generowanych odpowiedzi.</p>
</blockquote>
<p>Każdy z trzech etapów szczegółowo opisuję <strong>w kolejnych częściach pracy</strong>.</p>
<h1><strong>ROZDZIAŁ I. Toktumi 1.0. Pierwszy trening.</strong></h1>
<h1>1. Wybór rozmiaru modelu</h1>
<p>Spośród czterech dostępnych rozmiarów <strong>GPT-2 (XL, Large, Medium, Small)</strong>, zdecydowałem się na rozmiar <strong>Medium</strong>, ponieważ był to rozsądny kompromis pomiędzy posiadanymi zasobami sprzętowymi, a jakością generowanego tekstu. Granicę wyznaczała też ilość posiadanego vram (12 GB) na karcie graficznej NVIDIA RTX 3060. Uczenie modelu GPT-2 Medium w wybranej przeze mnie konfiguracji zużywało około 11 GB. Uznałem to za optymalny wybór.</p>
<p><strong>Dostępne rozmiary modeli GPT-2</strong></p>
<ul>
<li>GPT-2 Small: 124M, czyli: 124 miliony parametrów</li>
<li><strong>GPT-2 Medium: 345M, czyli: 345 milionów parametrów</strong> &lt;&lt; wybrany model</li>
<li>GPT-2 Large: 762M, czyli: 762 miliony parametrów</li>
<li>GPT-2 XL: 1,5B, czyli: 1,5 miliarda parametrów</li>
</ul>
<h1>2. Analiza rozwiązań OpenAI (GPT-2)</h1>
<p>Zacząłem od przeanalizowania dostępnych informacji o <strong>GPT-2</strong>, udostępnionych przez OpenAI w serwisach <a href="https://huggingface.co/openai-community/gpt2">Hugging Face</a> oraz <a href="https://github.com/openai/gpt-2/tree/master">GitHub</a>, aby wzorować się na rozwiązaniach zastosowanych przez twórców modelu.</p>
<p>Co warto wiedzieć: oryginalny model <strong>GPT-2, zaprezentowany przez OpenAI w lutym 2019 roku</strong>, był trenowany na zbiorze danych o rozmiarze około <strong>40 GB</strong>, o nazwie <strong>WebText</strong>, <a href="https://openai.com/index/better-language-models/">[źródło: OpenAI]</a>, <a href="https://huggingface.co/openai-community/gpt2">[źródło: Hugging Face]</a>. Zbiór ten zawierał około <strong>8 milionów próbek tekstu</strong>, zebranych ze stron internetowych. Częściowa lista tych adresów została opublikowana na <a href="https://github.com/openai/gpt-2/blob/master/model_card.md">GitHubie</a>. Samego zbioru jednak firma nie udostępniła. Na podstawie dostępnych źródeł można jednak dowiedzieć się czegoś o jego zawartości, jak i o stosowanej przez Open AI metodologii.</p>
<p>Dodatkową inspiracją były też istniejące wówczas modele GPT-2 w języku polskim, które przygotowali <strong>Sławomir Dadas</strong> <a href="https://huggingface.co/sdadas/polish-gpt2-xl/tree/main">polish-gpt2</a> oraz <strong>Dariusz Kleczek</strong> <a href="https://huggingface.co/dkleczek/papuGaPT2">papuGaPT2</a>.</p>
<h1>3. Zebranie danych</h1>
<p>Wybrane źródła:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/c060382e-5c79-4736-ba67-d772cd05d878.png" alt="003_logos_all" style="display:block;margin:0 auto" />

<p>Do uczenia <strong>Toktumi 1.0</strong> użyłem <strong>3 115 002 próbek tekstów</strong>, zebranych przeze mnie z następujących źródeł: (<strong>Wykop, Wikipedia , Fakt, Pudelek, Polsat News, książki, dowcipy</strong>).</p>
<p>Celem było zgromadzenie tekstów reprezentujących <strong>żywy, potoczny język</strong> i stworzenie modelu, który potrafi <strong>prowadzić konwersację</strong> na różnorodne tematy, używając stylu <strong>mowy potocznej</strong>.</p>
<p>Na tym etapie nie wiedziałem jeszcze, jakie rodzaje tekstów najlepiej sprawdzą się w zbiorze treningowym. Dobór materiałów miał więc częściowo charakter eksperymentalny — wiele decyzji podejmowałem <strong>metodą prób i błędów</strong>, stopniowo poznając możliwości modelu i kierunki jego dalszego rozwoju.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/6e8a2532-b0f0-44ce-a1c9-dbc129748e4c.png" alt="006_wykres_teksty" style="display:block;margin:0 auto" />

<p>a) <a href="https://pl.wikipedia.org"><strong>Wikipedia</strong></a> - wykorzystałem zasoby (data dump) polskiej wersji Wikipedii, udostępniane przez Fundację Wikimedia <a href="https://dumps.wikimedia.org/plwiki/latest/">do pobrania</a> na licencji Creative Commons CC BY-SA 4.0. Następnie zawartość pliku XML przekonwertowałem do czystego tekstu.</p>
<p>Wyodrębniłem <strong>1 485 976 haseł</strong>, z czego ostatecznie wykorzystałem około połowy z nich <strong>(718 129)</strong> - żeby styl Wikipedii nie 'zdominował' stylu odpowiedzi modelu.</p>
<p>Teksty z Wikipedii mają charakter haseł encyklopedycznych - są bogate w informacje, skupione na faktach, napisane neutralnym językiem, bez emocjonalnego stylu lub przekazywania opinii.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/619920c7-866c-4342-a816-5a161b88a53a.png" alt="007_wikitext" style="display:block;margin:0 auto" />

<p>b) <a href="https://www.fakt.pl/"><strong>Fakt.pl</strong></a> - do pozyskania tekstów wykorzystałem różne metody <strong>web scrapingu</strong>, służące do automatycznego pobierania i przetwarzania zawartości stron internetowych. W tym celu przygotowałem skrypty w języku <strong>Python</strong>, korzystając z biblioteki <strong>Scrapy</strong> oraz dodatku <strong>Scrapy-Splash</strong>, uruchamianego w kontenerze <strong>Docker</strong>.</p>
<p>Skrypty automatycznie pobierały artykuły, wyodrębniały ich treść z kodu HTML i zapisywały każdy tekst do osobnego pliku TXT.</p>
<p>W ten sposób zebrałem <strong>573 445 próbek tekstu</strong>, gotowych do dalszego przetwarzania i trenowania modelu.</p>
<p>Portal Fakt publikuje teksty o różnorodnej tematyce, zarówno artykuły informacyjne i publicystyczne, jak i treści <strong>o bardziej tabloidowym charakterze</strong>. Często mają charakter sensacyjny, emocjonalny i zawierają opinie. Można tu znaleźć <a href="http://m.in">m.in</a>. artykuły, recenzje, teksty poradnikowe, horoskopy, czyli <strong>szeroki przekrój tematów</strong>. Teksty tego rodzaju pomagają modelowi osiągnąć <strong>bardziej potoczny styl</strong> oraz rozszerzyć jego wiedzę o świecie z różnych dziedzin.</p>
<p>Późniejsze testy i doświadczenia pokazały, że jest to <strong>jeden z najcenniejszych zasobów tekstów</strong> w całym projekcie, przede wszystkim ze względu na przystępny język, różnorodność gatunków wypowiedzi i bardzo szeroki zakres poruszanych tematów.</p>
<p>Mechanizm działania Scrapy:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/6182692a-53fa-41a2-87db-37962cd19967.png" alt="008_web_scraping" style="display:block;margin:0 auto" />

<p>c) <a href="https://www.polsatnews.pl/"><strong>PolsatNews.pl</strong></a> - do pozyskania tekstów z tego portalu użyłem tej samej metody (<strong>Scrapy, Splash, Docker</strong>).</p>
<p>Wyzwaniem było ustalenie listy adresów URL do pobrania, ponieważ portal jej nie udostępnia. W tym celu przygotowałem w Pythonie <strong>crawler</strong>, wykorzystujący biblioteki <strong>Requests</strong> i <strong>BeautifulSoup</strong>, który automatycznie wyszukiwał linki do artykułów. Zebrane adresy były podstawą do dalszego scrapowania treści z użyciem Scrapy i Splash. W ten sposób zgromadziłem <strong>126 004</strong> próbek tekstu.</p>
<p>Portal Polsatnews to teksty informacyjne <strong>o bieżących wydarzeniach krajowych i zagranicznych</strong>. Styl newsowy, formalny, rzeczowy. Artykuły zawierają wiadomości z oficjalnych sprawdzonych źródeł. Teksty tego rodzaju pomagają modelowi poszerzyć swoją <strong>wiedzę o świecie</strong> i nauczyć się posługiwania neutralnym językiem, ale innym niż styl Wikipedii.</p>
<p>d) <a href="https://www.pudelek.pl/"><strong>Pudelek.pl</strong></a> - do pozyskania tekstów wykorzystałem tę samą metodę web scrapingu (<strong>Scrapy, Splash, Docker</strong>). W ten sposób zebrałem <strong>165 229 próbek tekstu</strong>.</p>
<p>Pudelek zawiera <strong>teksty o charakterze plotkarskim</strong> - krótkie artykuły o celebrytach, gwiazdach i show-biznesie. Pisane charakterystycznym językiem pełnym ironii, złośliwości, często grające na emocjach. Teksty tego rodzaju pomagają modelowi w imitowaniu żywego potocznego języka, a 'pudelkowy' styl od razu łatwo rozpoznać.</p>
<p>Pudelek publikuje też charakterystyczne, często kąśliwe, <strong>komentarze użytkowników</strong>. Komentarze zostały pobrane razem z treścią artykułów. Ostatecznie jednak ich nie użyłem, ponieważ pojawiają się na stronie w sposób losowy, przez co model, ucząc się na ich podstawie, nie zachowywał logicznego związku pomiędzy promptem a odpowiedzią.</p>
<p>e) <a href="https://wykop.pl/"><strong>Wykop</strong></a> - to popularny portal, na którym użytkownicy dzielą się swoimi internetowymi „znaleziskami”, publikują posty, a inni użytkownicy komentują je i oceniają. W kontekście trenowania modelu uznałem go za cenne źródło <strong>tekstów w stylu komentarzy internetowych</strong>.</p>
<p>Do pozyskania tekstów wykorzystałem <strong>Python</strong> oraz bibliotekę <strong>Selenium</strong> z narzędziem <strong>GeckoDriver</strong>, umożliwiające automatyzację przeglądarki Firefox. W ten sposób zebrałem <strong>1 413 999 próbek tekstów</strong>, obejmujących publikacje wraz z komentarzami użytkowników.</p>
<p>Ostatecznie okazało się, że <strong>wykorzystanie komentarzy internetowych, podobnie jak tych z Pudelka, nie było dobrym wyborem</strong>. Komentarze często zawierały chaotyczne, obraźliwe lub niezwiązane z tematem wypowiedzi, które negatywnie wpływały na jakość odpowiedzi modelu. Dlatego w kolejnych wersjach Toktumi <strong>całkowicie zrezygnowałem z tego źródła danych</strong>.</p>
<p>Mechanizm działania web scrapingu z Selenium:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/03e6e65e-85ff-400b-ba02-f6f348ce1198.png" alt="009_Selenium" style="display:block;margin:0 auto" />

<p>f) <strong>Dowcipy</strong> - do zbioru uczącego postanowiłem też dodać teksty dowcipów, zebranych z kilkunastu ogólnodostępnych stron internetowych. Był to eksperyment, który miał sprawdzić, czy model nauczy się generować sensowne teksty humorystyczne.</p>
<p>Okazało się jednak, że nauczenie modelu humoru jest znacznie trudniejsze, niż początkowo zakładałem. Wymaga bowiem uchwycenia ironii, gry słów i różnych subtelności językowych. Niespodziewanie jednak <strong>dowcipy pomogły modelowi przyswoić strukturę pytanie-odpowiedź (QA)</strong>, czyli zasadę, że na zadane pytanie należy udzielić odpowiedzi. Tego rodzaju struktura często występuje w dowcipach, podczas gdy pozostałe teksty w zbiorze treningowym na ogół jej nie zawierały.</p>
<p>Po usunięciu duplikatów zebrałem <strong>88 897 dowcipów</strong>.</p>
<p>Zbiór wymagał również <strong>dodatkowego czyszczenia i filtrowania treści obraźliwych lub nieodpowiednich</strong>. Dowcipy często publikowane są bezpośrednio przez internautów, bez odpowiedniej moderacji. Próby usunięcia takich treści nie zawsze przynosiły zadowalające rezultaty. Pokazało to, jak trudne jest przygotowanie wartościowego zbioru treningowego z tekstów pochodzących z otwartych źródeł internetowych.</p>
<p>Do pobierania tekstów wykorzystałem zautomatyzowaną obsługę przeglądarki (<strong>Firefox, GeckoDriver, Selenium i Python</strong>).</p>
<p>g) <strong>Książki</strong> - do zbioru na koniec dodałem teksty <strong>285 książek</strong> znalezionych w sieci, podzielonych na <strong>29 299 fragmentów</strong>. Były to książki typu: <strong>instrukcje obsługi, poradniki, książki popularnonaukowe, reportaże, książki kucharskie</strong> etc. Celem było dostarczenie dodatkowych, bardziej pogłębionych treści.</p>
<p>Okazało się, że do uczenia nie nadaje się beletrystyka i szeroko rozumiana fikcja: utwory teatralne, poezja, a także napisy do filmów. Jak pokazały testy, model wówczas zaczynał posługiwać się zbyt abstrakcyjnym, wyrwanym z kontekstu, niezrozumiałym językiem, lub mówił stylem powieściowym, który utrudnia swobodną konwersację. Dlatego, przy wyborze tekstów z tej kategorii, ograniczyłem się do twórczości non-fiction i utworów komentujących naszą rzeczywistość.</p>
<p>Teksty książek trzeba było podzielić na mniejsze fragmenty, zachowując przy tym <strong>szerszy kontekst wypowiedzi</strong>. W tym celu zastosowałem metodę przesuwających się okien „z zakładką” (<strong>overlapping sliding window</strong>), w której końcówka jednego fragmentu powtarza się na początku kolejnego. Dzięki temu udało się zachować ciągłość tekstu i ograniczyć utratę kontekstu.</p>
<p>Dzielenie danych metodą overlappingu:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/36e3f2d5-1835-4be2-97e1-c59cd172ec9f.png" alt="010_overlapping" style="display:block;margin:0 auto" />

<h1>4. Stworzenie pliku treningowego (training set)</h1>
<p>Każdy tekst zapisywałem <strong>do osobnego pliku .txt</strong>. Wybrałem ten format ze względu na jego prostotę — pliki tekstowe można łatwo przeglądać, edytować i czyścić, bez konieczności stosowania dodatkowej struktury danych, np. JSON. Umożliwia to łatwiejsze zapanowanie nad zbiorem danych. Dzięki temu można było np. uporządkować pliki <strong>według rozmiarów</strong> i usunąć te najmniejsze - czyli puste albo nieprzydatne z punktu widzenia uczenia modelu.</p>
<p>Największe pliki .txt, przekraczające <strong>1024 tokenów</strong>, dzieliłem na mniejsze części metodą overlappingu.</p>
<p>Z kolei krótsze teksty tego samego typu (np. dowcipy, krótsze artykuły) łączyłem w większe 'paczki', liczące sobie w przybliżeniu 1024 tokenów, aby zbiór do uczenia był dobrze zbilansowany. Każdy tekst wewnątrz był oddzielony separatorem.</p>
<p>Łączenie tekstów tego samego rodzaju w większe pakiety:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/cde712c8-4536-4ead-a639-20c57e6e1ecb.png" alt="011_łączenie" style="display:block;margin:0 auto" />

<p>Każdy ze zbiorów danych należało też <strong>oczyścić</strong>. Był to proces bardzo czasochłonny, składający się z wielu etapów, których nie będę w tym miejscu szczegółowo opisywał. Warto jednak podkreślić, że czyszczenie i przygotowanie danych <strong>zajmuje najwięcej czasu - więcej niż samo pozyskiwanie danych i trenowanie modelu</strong>.</p>
<p>Pokazuje to, jak <strong>ogromnych nakładów pracy wymaga ten etap</strong>, który początkowo może wydawać się mniej istotny, a w rzeczywistości jest jednym z najbardziej czasochłonnych i kluczowych elementów całego procesu uczenia modelu. Warto pamiętać o zasadzie <strong>„Garbage In, Garbage Out”</strong> - jakość danych wejściowych ma fundamentalny wpływ na jakość uzyskiwanych wyników.</p>
<p>Kolejnym krokiem była <strong>redukcja zbioru wykorzystywanych znaków</strong> w zebranych tekstach. W tym celu wszystkie teksty przekonwertowałem z kodowania <strong>UTF-8</strong> do <strong>Windows-1250</strong> (do 256 znaków) - i z powrotem (aby zachować ich zgodność z narzędziami wykorzystywanymi podczas treningu). Przy takiej konwersji i dodatkowym czyszczeniu, znaki spoza tego zestawu zostały zastąpione lub usunięte.</p>
<p>Dzięki temu model GPT-2 nie musiał przetwarzać wielu nietypowych znaków, które nie wnosiły istotnej wartości językowej. Ograniczenie liczby wykorzystywanych znaków miało również na celu <strong>usprawnienie działania tokenizera</strong> (w moim przypadku <strong>rozmiar słownika to 50 000 tokenów</strong>).</p>
<p>Redukcja korpusu znaków podczas konwersji z UTF-8 do ANSI:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/722e19db-0c3b-4c99-acce-c0e9baace3bf.png" alt="012_ansi" style="display:block;margin:0 auto" />

<p>W następnym etapie wszystkie pliki .txt z tekstami połączyłem w <strong>jeden duży zbiór danych treningowych (training set)</strong>, który następnie został użyty do uczenia modelu. Każda z <strong>ponad 3 milionów</strong> próbek tekstu została oddzielona separatorem <code>&lt;|endoftext|&gt;</code> - takim samym, jak zastosowany przez Open AI w oryginalnej wersji modelu GPT-2.</p>
<p>Wydzieliłem też mniejszy <strong>zbiór walidacyjny (validation set)</strong> - zawierający dane w tym samym formacie, ale które <strong>nie powtarzają się</strong> w zbiorze treningowym. Wykorzystałem go później do oceny jakości modelu (do pomiaru <strong>perplexity</strong>).</p>
<p>Metoda podziału tekstów w zbiorze treningowym i walidacyjnym:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/29bae01f-ac02-4673-b7ac-ab6488499e11.png" alt="013_train_dataset" style="display:block;margin:0 auto" />

<h3>ROZMIARY ZBIORU TRENINGOWEGO TOKTUMI 1.0 (bez separatorów):</h3>
<blockquote>
<p>Liczba próbek tekstu: <strong>3 115 002</strong></p>
<p>Liczba słów: <strong>658 078 092</strong></p>
<p>Liczba znaków: <strong>4 480 020 887</strong></p>
<p>Liczba tokenów: <strong>978 054 512</strong></p>
<p>Rozmiar pliku: <strong>4,46 GB</strong></p>
</blockquote>
<h1>5. Przygotowanie tokenizera</h1>
<p><strong>Tokenizer (tokenizator)</strong> to zestaw plików i reguł pomagających modelowi przekształcać tekst na tokeny - czyli mniejsze fragmenty, na które dzieli się tekst. To właśnie na tokenach działa model, zamieniając je <strong>na wartości liczbowe</strong>, a na końcu z powrotem na tekst. Właściwe przygotowanie tokenizera ma kluczowe znaczenie dla spójności danych, jakości generowanych odpowiedzi i ogólnej wydajności modelu. Źle przygotowany tokenizer może znacznie pogorszyć proces uczenia i jakość generowanych tekstów.</p>
<p>Zamiast korzystać z gotowego tokenizera, zdecydowałem się przygotować własny, dopasowany do języka polskiego, na podstawie dużej próbki zebranych wcześniej tekstów.</p>
<p>Pliki najczęściej wchodzące w skład tokenizera: <strong>vocab.json, merges.txt, tokenizer_config.json, special_tokens_map.json, tokenizer.json</strong>.</p>
<p>Zawartość pliku vocab.json:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/0408c30e-57a5-4564-b232-70a925f2d4fc.png" alt="014_tokenizer" style="display:block;margin:0 auto" />

<p>Poniżej kluczowy fragment <strong>kodu użytego do utworzenia tokenizera</strong>.</p>
<p>Przyjąłem rozmiar słownika <strong>vocab_size=50_000</strong>, zbliżony do oryginalnego tokenizera GPT-2, który zawierał 50 257 tokenów we wszystkich wariantach modelu.</p>
<p>Do utworzenia tokenizera wykorzystałem biblioteki <strong>Tokenizers</strong> oraz <strong>Transformers</strong>.</p>
<pre><code class="language-python">from tokenizers import ByteLevelBPETokenizer
from transformers import GPT2TokenizerFast

# Utworzenie i trening własnego tokenizera BPE
tokenizer = ByteLevelBPETokenizer()

tokenizer.train(
    files="tokenizer_teksty.txt",
    vocab_size=50_000,
    min_frequency=2,
    special_tokens=[
        "&lt;|endoftext|&gt;",
        "&lt;unk&gt;",
        "[PAD]"
    ]
)

# Zapis słownika i reguł BPE
tokenizer_path = "tokenizer_2025_endoftext"
tokenizer.save_model(tokenizer_path)

# Przygotowanie tokenizera do pracy z Transformers
fast_tokenizer = GPT2TokenizerFast(
    vocab_file=f"{tokenizer_path}/vocab.json",
    merges_file=f"{tokenizer_path}/merges.txt",
    unk_token="&lt;unk&gt;",
    pad_token="[PAD]",
    eos_token="&lt;|endoftext|&gt;"
)

fast_tokenizer.save_pretrained(tokenizer_path)
</code></pre>
<p>Po utworzeniu tokenizera należało sprawdzić, czy działa prawidłowo. W tym celu przeprowadziłem testy poprawności kodowania i dekodowania tekstu oraz zweryfikowałem, czy token specjalny <code>&lt;|endoftext|&gt;</code> jest prawidłowo rozpoznawany jako separator. Wszystkie testy zakończyły się pomyślnie.</p>
<p>Proces tokenizacji i jej weryfikacja:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/794bd289-299b-4be9-872f-8af7b57dfe37.gif" alt="018_tokenizer" style="display:block;margin:0 auto" />

<h1>6. Utworzenie pustego modelu GPT-2 Medium</h1>
<p>W kolejnym kroku utworzyłem <strong>pusty model GPT-2</strong>, bez wytrenowanych wag. Konfiguracja modelu (liczba warstw, głowic, wymiar osadzeń, długość kontekstu i rozmiar słownika) są dopasowane do <strong>wariantu GPT-2 Medium</strong> oraz wcześniej przygotowanego tokenizera. Jest to punkt wyjścia do trenowania GPT-2 od zera.</p>
<pre><code class="language-python">from transformers import GPT2LMHeadModel, GPT2Config

# Konfiguracja architektury GPT-2 Medium
config_medium = GPT2Config(
    vocab_size=50_000,  # Rozmiar słownika
    n_positions=1024,   # Maksymalna długość sekwencji
    n_ctx=1024,         # Długość kontekstu
    n_embd=1024,        # Wymiar osadzeń
    n_layer=24,         # Liczba warstw
    n_head=16,          # Liczba głowic atencji
    bos_token_id=0,
    eos_token_id=0,
    pad_token_id=2
)

# Inicjalizacja modelu z losowymi wagami
model_medium = GPT2LMHeadModel(config_medium)

# Zapis modelu i jego konfiguracji
model_medium.save_pretrained("GPT2-MEDIUM-empty_new")
</code></pre>
<h1>7. Trening modelu</h1>
<p>Model trenowałem lokalnie, na karcie <strong>NVIDIA GeForce RTX 3060 (12 GB VRAM)</strong>, z wykorzystaniem <strong>Pythona, PyTorch, Transformers i CUDA</strong>. Jednym z pierwszych wyzwań było dobranie kompatybilnych wersji bibliotek i sterowników — niezgodności często powodowały błędy lub uniemożliwiały wykorzystanie GPU.</p>
<p>Po skonfigurowaniu środowiska należy sprawdzić, czy PyTorch prawidłowo wykrywa CUDA:</p>
<pre><code class="language-python">import torch

print("Czy CUDA dostępne =", torch.cuda.is_available())
</code></pre>
<p><strong>Wykorzystanie GPU znacząco przyspiesza trening modelu</strong> w porównaniu z obliczeniami na samym procesorze (CPU).</p>
<h3>UCZENIE - PODSTAWOWE WYZWANIA:</h3>
<p>Poniżej fragment logu z przebiegu <strong>treningu</strong>. Wskaźniki po lewej stronie przedstawiają liczbę wykonanych <strong>kroków</strong> oraz łączną liczbę kroków zaplanowanych w całym treningu (<strong>1140000/1771840</strong>), co oznacza, że wykonano <strong>1 140 000</strong> kroków z planowanych <strong>1 771 840</strong>. Po prawej stronie widoczna jest wartość 'epoch': 6.43, która informuje, że model znajduje się w <strong>szóstej epoce</strong>. Cały proces uczenia został zaplanowany na <strong>10 epok</strong>, więc ogólny postęp na tym etapie wynosi około <strong>64%</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/81063391-741a-4241-ac79-3c9e03bb2196.png" alt="015_epoki_Toktumi1" style="display:block;margin:0 auto" />

<p>Kluczowe pojęcia pozwalające monitorować postęp uczenia:</p>
<ul>
<li><strong>Kroki (steps)</strong> określają, ile razy zaktualizowano wagi modelu. Jeden krok obejmuje przetworzenie jednej lub kilku partii danych (batchy), obliczenie błędu i aktualizację parametrów modelu.</li>
<li><strong>Epoki (epochs)</strong> wskazują, ile razy został przetworzony cały zbiór treningowy. Jedna epoka to jedno pełne przejście przez cały zbiór danych uczących. Zazwyczaj potrzeba wielu epok, aby model osiągnął dobrą jakość predykcji.</li>
</ul>
<p>Zanim powstał ostateczny kod do uczenia modelu, trzeba było przeprowadzić wiele tur testów na mniejszych zbiorach danych (pliki tekstowe około 50-100 MB). Jednym z poważniejszych wyzwań była odpowiedź na pytanie: <strong>po ilu epokach zakończyć szkolenie?</strong></p>
<p>Na początku testowałem kod z mechanizmem <strong>early stopping</strong> i dwoma zbiorami, treningowym (training set) i walidacyjnym (validation set). Automatyczne zatrzymanie uczenia następowało <strong>około 7. epoki</strong>. Na tej podstawie przyjąłem bezpieczny limit - maksymalnie 10 epok - jako wartość końcową w docelowym kodzie. Ostatecznie zrezygnowałem z mechanizmu early stopping, ponieważ okazał się mało praktyczny przy trenowaniu na dużym zbiorze danych, w wielu turach, gdy proces musiał być dzielony na mniejsze etapy (wiele razy wstrzymywany i wznawiany).</p>
<p>W ustaleniu najlepszego momentu na zakończenie uczenia bardziej praktyczne okazało się mierzenie <strong>perpleksji (perplexity)</strong>, o czym bardziej szczegółowo piszę w dalszej części.</p>
<p>Poniżej najważniejsza część <strong>kodu wykorzystanego do treningu Toktumi 1.0</strong>, wraz z <strong>konfiguracją hiperparametrów</strong>.</p>
<pre><code class="language-python">from transformers import (
    GPT2LMHeadModel, GPT2Tokenizer, TextDataset,
    DataCollatorForLanguageModeling, Trainer, TrainingArguments
)

# Model, tokenizer i dane treningowe
model_path = "GPT2-MEDIUM-empty_new"
tokenizer_path = "tokenizer_2025_endoftext"
train_path = "BIG_UCZ_fin_v2.txt"
output_dir = "GPT2-MEDIUM-Toktumi-1"

model = GPT2LMHeadModel.from_pretrained(model_path)
tokenizer = GPT2Tokenizer.from_pretrained(tokenizer_path)

# Dopasowanie tokenizera do modelu
tokenizer.add_special_tokens({"eos_token": "&lt;|endoftext|&gt;"})
model.resize_token_embeddings(len(tokenizer))

# Przygotowanie danych treningowych
train_dataset = TextDataset(
    tokenizer=tokenizer,
    file_path=train_path,
    block_size=230  # Długość sekwencji dopasowana do pamięci GPU
)

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False  # Uczenie przewidywania kolejnego tokenu
)

# Hiperparametry treningu
training_args = TrainingArguments(
    output_dir=output_dir,
    overwrite_output_dir=True,
    num_train_epochs=10,             # Maksymalna liczba epok
    per_device_train_batch_size=8,   # Wielkość batcha
    gradient_accumulation_steps=3,   # Akumulacja gradientów
    learning_rate=5e-5,              # Współczynnik uczenia
    fp16=True,                       # Mieszana precyzja (oszczędność VRAM)
    save_steps=7500,                 # Zapis checkpointu co 7500 kroków
    save_total_limit=300,            # Limit zapisanych checkpointów
    logging_steps=500,               # Logowanie postępu
    save_safetensors=False
)

# Inicjalizacja treningu
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator
)

# Trening od zera
trainer.train()

# Alternatywnie: wznowienie z ostatniego checkpointu
# trainer.train(resume_from_checkpoint=True)

# Zapis wytrenowanego modelu i tokenizera
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)
</code></pre>
<p>W trakcie procesu uczenia niezwykle przydatna okazała się funkcja <strong>automatycznego zapisywania checkpointów</strong> (w tym przypadku <strong>'save_steps=7500'</strong>, czyli co 7500 kroków).</p>
<p>Katalog z checkpointami:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/7888cab7-4086-4eba-b075-638fe2b8bf27.png" alt="016_checkpointy" style="display:block;margin:0 auto" />

<p>Każdy katalog z checkpointem zawiera pełny stan modelu, a także niezbędne pliki tymczasowe. Dzięki checkpointom możliwe jest <strong>wznowienie treningu dokładnie od miejsca, w którym został zapisany</strong>. Zapisywanie wielu checkpointów wymaga jednak dużo przestrzeni dyskowej.</p>
<p>Jest to szczególnie istotne w przypadku długiego treningu trwającego wiele dni — kiedy mogą wystąpić przerwy w zasilaniu, konieczność ponownego uruchomienia komputera lub inne nieprzewidziane zdarzenia.</p>
<h3>ZAKOŃCZENIE TRENINGU</h3>
<p>Ostatecznie, najbardziej optymalnym punktem zakończenia uczenia okazał się <strong>checkpoint-1140000</strong>, czyli około <strong>6,44 epoki</strong>.</p>
<p>Czas treningu: <strong>około 380 godzin (ponad 15 dni)</strong>.</p>
<h1>8. Ocena jakości modelu. Perplexity.</h1>
<p>Do oceny jakości wytrenowanego modelu GPT-2 Medium wykorzystałem wskaźnik <strong>perplexity (perpleksja)</strong>. Perplexity mierzy, jak dobrze model przewiduje kolejne słowa w sekwencji — <strong>im niższa wartość perpleksji, tym lepsze dopasowanie</strong> modelu do danych.</p>
<p>Model był oceniany z użyciem wydzielonego wcześniej <strong>zbioru walidacyjnego (validation set)</strong> czyli danych, których model wcześniej 'nie widział'. Wynik jest obliczany na podstawie funkcji trainer.evaluate(), która zwraca wartość straty (loss).</p>
<pre><code class="language-python">perplexity = math.exp(eval_results["eval_loss"])
</code></pre>
<p>Ze względu na techniczną złożoność tego zagadnienia, skorzystałem z <a href="https://www.comet.com/site/blog/perplexity-for-llm-evaluation/">gotowych rozwiązań</a>.</p>
<p>Zastosowane rozwiązanie działa następująco: kod przechodzi kolejno <strong>przez zapisane checkpointy</strong> i dla wybranych z nich oblicza wartość <strong>perplexity</strong>. Dzięki temu można oszacować, w którym momencie <strong>dalsze uczenie przestaje być opłacalne</strong>.</p>
<p>Jak widać na poniższym wykresie, w pewnym momencie spadek perplexity wyraźnie wyhamował, a dalsze trenowanie oznaczałoby duże zużycie zasobów obliczeniowych przy bardzo niewielkiej poprawie. Na tej podstawie podjąłem decyzję o wcześniejszym zakończeniu treningu, zanim uczenie osiągnęło zaplanowane 10 epok.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fc1350cc-e1fa-4aeb-98be-fd51c907b8da.png" alt="17_perplexity_Toktumi1" style="display:block;margin:0 auto" />

<h1>9. Ocena jakości modelu. Prompting.</h1>
<p>Drugim etapem oceny jakości modelu była <strong>ewaluacja ludzka</strong>, czyli sprawdzenie, jak Toktumi radzi sobie z konkretnymi promptami.</p>
<p>Do testów wykorzystałem <strong>greedy decoding</strong> — metodę, w której model wybiera najbardziej prawdopodobny kolejny token. Dzięki temu na ten sam prompt otrzymywałem zawsze tę samą odpowiedź, co zapewniało stabilny punkt odniesienia przy porównywaniu checkpointów.</p>
<p>Taki efekt można uzyskać, ustawiając <code>do_sample=False</code> lub, tak jak w moich testach, <code>do_sample=True</code> <strong>i</strong> <code>top_k=1</code>. Pozostałe parametry pozwalają kontrolować długość odpowiedzi i ograniczać powtarzanie fragmentów tekstu.</p>
<p>Poniżej kluczowy fragment kodu wykorzystanego do generowania i testowania odpowiedzi Toktumi 1.0.</p>
<pre><code class="language-python">import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

# Wczytanie modelu Toktumi 1.0 i tokenizera
model_path = "GPT2-MEDIUM-Toktumi-1"
model = GPT2LMHeadModel.from_pretrained(model_path)
tokenizer = GPT2Tokenizer.from_pretrained(model_path)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device).eval()

# Przykładowy prompt
prompt = "Co to jest grawitacja?"
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)

# Parametry generowania odpowiedzi
generate_args = {
    "max_length": 256,            # Maksymalna długość: prompt + odpowiedź
    "min_new_tokens": 2,          # Minimum 2 nowe tokeny
    "no_repeat_ngram_size": 2,    # Blokowanie powtarzających się par tokenów
    "repetition_penalty": 1.1,    # Kara za powtarzanie tokenów
    "do_sample": True,            # Włączony tryb samplingu
    "top_k": 1,                   # Wybór tylko najbardziej prawdopodobnego tokenu
    "pad_token_id": tokenizer.eos_token_id,
    "eos_token_id": tokenizer.eos_token_id
}

# Generowanie i dekodowanie odpowiedzi
with torch.no_grad():
    output = model.generate(input_ids, **generate_args)

generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)
</code></pre>
<p>Model udziela najbardziej prawdopodobnej odpowiedzi.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest grawitacja?</em></p>
<p><strong>Odpowiedź:</strong> <em>- To jest siła, która przyciąga przedmioty.</em></p>
</blockquote>
<p>Aby znaleźć <strong>najlepszy checkpoint</strong> pod względem jakości odpowiedzi na prompty, przetestowałem wybrane checkpointy na zestawie <strong>150 promptów</strong> o różnej strukturze: <strong>dokończenia zdań, pytania i instrukcje</strong>:</p>
<ul>
<li><strong>Dokończenia zdań</strong>, np. „Warszawa jest stolicą...”, „Ziemia krąży wokół...”. Model na ogół prawidłowo odczytywał, że jest to struktura, w której należy dokończyć zdanie. Często też udzielał prawidłowej lub najbardziej „intuicyjnej” odpowiedzi. Tu zdarzało się najmniej błędów.</li>
<li><strong>Pytania</strong>, zakończone znakiem zapytania, np. „Co to jest planeta?”, „Do czego służy ołówek?”. Model powinien udzielić odpowiedzi na pytanie - tak jak robi to człowiek. Często prawidłowo odczytywał sytuację pytanie-odpowiedź. Często jednak miał problem zarówno ze zrozumieniem struktury (na pytanie trzeba odpowiedzieć), jak i z faktografią oraz wiedzą o świecie (odpowiadał, ale niepoprawnie).</li>
<li><strong>Instrukcje</strong>, np. „Wymień trzy warzywa”, „Podaj przykład pojazdu”. Warto zauważyć, że instrukcja nie jest pytaniem, ale również wymaga odpowiedniej reakcji — w tym przypadku wykonania polecenia lub udzielenia odpowiedzi. Ten typ promptów sprawiał modelowi największą trudność, ponieważ często nie rozpoznawał ich jako formy wymagającej odpowiedzi.</li>
</ul>
<p>Pełna lista 150 promptów i odpowiedzi modelu Toktumi 1.0 (w konfiguracji 'greedy decoding') -&gt; <a href="https://huggingface.co/datasets/miczac/toktumi-reports/raw/main/Toktumi_1.0_150_prompts_checkpoint-1140000.txt"><strong>ZOBACZ</strong></a>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/870b69c3-5e1e-4c1b-970d-72a89a242abe.png" alt="020_prompty" style="display:block;margin:0 auto" />

<h1>10. Zakończenie treningu</h1>
<p>Po analizie <strong>perplexity</strong> i wyników testów na <strong>150 promptach</strong>, za najlepszy uznałem <strong>checkpoint-1140000</strong>, zapisany po około <strong>6,44 epoki</strong>.</p>
<p>Dalsze uczenie nie przynosiło już wystarczającej poprawy jakości odpowiedzi, dlatego zdecydowałem się zakończyć trening na tym etapie.</p>
<p>Wybrany checkpoint zawiera zapisane wagi modelu. To on stanowi podstawę finalnej wersji <strong>Toktumi 1.0</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/a9e0b84a-95ab-4893-a73c-49f1f15e6673.png" alt="021_model_ready" style="display:block;margin:0 auto" />

<h1>11. Końcowa ewaluacja modelu</h1>
<p>Pozostała jeszcze <strong>ewaluacja końcowa</strong> i ustalenie, w jakim stopniu model realizuje przyjęte na początku założenia, czyli <strong>czy potrafi prowadzić konwersację na różnorodne tematy, używając stylu mowy potocznej</strong>.</p>
<p>W tym miejscu ograniczę się tylko do najważniejszych spostrzeżeń. Prezentowane poniżej przykłady były generowane w trybie <strong>greedy decoding</strong>.</p>
<h3>a) Rozpoznanie promptu.</h3>
<p><strong>WNIOSKI:</strong> Model często rozpoznaje strukturę promptu i generuje poprawną odpowiedź <strong>pod względem gramatycznym i składniowym</strong>. Używa przy tym <strong>właściwie dobranego stylu</strong>. Co ciekawe, struktury <strong>Pytanie-Odpowiedź</strong> model nauczył się głównie ze zbioru z dowcipami - wiele dowcipów ma formę pytania, po którym następuje odpowiedź (puenta). Dzięki temu model przyswoił prostą zasadę: <strong>gdy pada pytanie, należy na nie odpowiedzieć</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest grawitacja?</em></p>
<p><strong>Odpowiedź:</strong> <em>- To jest siła, która przyciąga przedmioty.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co robi kucharz?</em></p>
<p><strong>Odpowiedź:</strong> <em>- Smaży kotlety.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Warzywa są zdrowe, bo</em></p>
<p><strong>Odpowiedź:</strong> <em>Warzywa są zdrowe, bo zawierają dużo witamin i minerałów.</em></p>
</blockquote>
<h3>b) Podstawowa wiedza o świecie.</h3>
<p><strong>Gdy prompt jest stosunkowo prosty</strong>, dotyczy powszechnie znanych faktów i nie wymaga złożonego rozumowania, <strong>model często udziela poprawnej odpowiedzi</strong>. Posiada więc <strong>ogólną wiedzę o świecie</strong> i znajomość <strong>podstawowych faktów</strong>.</p>
<p>Najlepiej radzi sobie z promptami wymagającymi <strong>dokończenia zdania</strong> - ta forma jest dla niego najprostsza do zdekodowania.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Stolicą Francji jest</em></p>
<p><strong>Odpowiedź:</strong> <em>- Stolicą Francji jest Paryż.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Najwyższą górą na świecie jest</em></p>
<p><strong>Odpowiedź:</strong> <em>Najwyższą górą na świecie jest Mount Everest, który ma wysokość 720 metrów.</em></p>
</blockquote>
<p>(choć myli się podając wysokość)</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Najdłuższą rzeką w Polsce jest</em></p>
<p><strong>Odpowiedź:</strong> <em>Najdłuższą rzeką w Polsce jest rzeka Wisła.</em></p>
</blockquote>
<h3>c) Błędne odpowiedzi.</h3>
<p>Zdarza się, że model generuje odpowiedź o poprawnej strukturze, lecz niezgodną z prawdą — sprawiając wrażenie, jakby „zmyślał”. Zjawisko to określa się terminem <strong>halucynacji</strong>. Oznacza ono sytuację, w której model przedstawia nieprawdziwe lub wymyślone informacje tak, jakby były faktami.</p>
<p>Jak pokazują testy, halucynacje częściej pojawiają się w <strong>słabiej wytrenowanych modelach</strong>, zwłaszcza na wczesnych etapach uczenia. Trochę przypomina to <strong>zachowanie człowieka, który nie zna odpowiedzi, ale mimo to próbuje ją odgadnąć</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Do czego służy ołówek?</em></p>
<p><strong>Odpowiedź:</strong> <em>- Do czyszczenia akwariów.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Z czego robi się mąkę?</em></p>
<p><strong>Odpowiedź:</strong> <em>- Z proszku do pieczenia.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Najszybszym zwierzęciem na lądzie jest</em></p>
<p><strong>Odpowiedź:</strong> <em>Najszybszym zwierzęciem na lądzie jest koń.</em></p>
</blockquote>
<h3>d) Brak zrozumienia.</h3>
<p>Czasem model niewłaściwie odczytuje strukturę pytania albo udziela odpowiedzi wewnętrznie sprzecznej.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Księżyc świeci w</em></p>
<p><strong>Odpowiedź:</strong> <em>Księżyc świeci w nocy, a księżyc świeci nocą.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co to jest ogień?</em></p>
<p><strong>Odpowiedź:</strong> <em>- To jest taki mały, biały, czerwony, świecący, czarny, żółty, zielony, niebieski, pomarańczowy, brązowy, fioletowy.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie jedzenie przygotowujesz, kiedy masz gości?</em></p>
<p><strong>Odpowiedź:</strong> <em>- Na początku tygodnia.</em></p>
</blockquote>
<p>Dodatkowo: gdy prompt zawiera <strong>instrukcje</strong>, model bardzo często w ogóle <strong>nie potrafi rozpoznać oczekiwanego stylu odpowiedzi</strong>.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Podziel się swoimi planami na weekend.</em></p>
<p><strong>Odpowiedź:</strong> <em>Horoskop dzienny - Lew. Dzisiejszy dzień przyniesie Ci wiele możliwości do rozwoju osobistego i zawodowego...</em></p>
</blockquote>
<p>(nieoczekiwanie odpowiada w stylu horoskopu)</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Wymień dwie przyprawy.</em></p>
<p><strong>Odpowiedź:</strong> <em>Wymień dwie przyprawy. To proste!</em></p>
</blockquote>
<p>(cytuje fragment artykułu z Pudelka lub Faktu)</p>
<h3>e) Sekcja 'Komentarz:'</h3>
<p>Jak wspomniałem wcześniej, teksty z serwisu '<a href="http://Wykop.pl">Wykop.pl</a>' zostały dołączone do zbioru treningowego Toktumi 1.0 razem z komentarzami. Był to najliczniejszy zbiór tekstów w zbiorze treningowym (<strong>1 413 999 próbek</strong>).</p>
<p>Testy pokazały, że komentarze internetowe znacząco pogorszyły jakość generowanych odpowiedzi. Rzadko były na temat, a dodatkowo - pomimo moderacji - nadal zawierały <strong>oceniający, obraźliwy język, powielały szkodliwe stereotypy, a nawet zawierały teorie spiskowe</strong>.</p>
<p>Model nie tyle błędnie interpretuje polecenie, ile <strong>naśladuje styl komentarzy internetowych</strong> — często chaotyczny, oceniający i nieodpowiedni dla roli przyjaznego asystenta.</p>
<blockquote>
<p><strong>Prompt:</strong> <em>Jakie znasz środki transportu?</em></p>
<p><strong>Odpowiedź:</strong> <em>(...)</em></p>
<p><strong>Komentarz:</strong> <em>Nie znam, ale się wypowiem.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Co sądzisz o podróżach po Europie?</em></p>
<p><strong>Odpowiedź:</strong> <em>(...)</em></p>
<p><strong>Komentarz:</strong> <em>Nie wiem, czy to dobry pomysł. W Polsce jest dużo taniej niż w Niemczech.</em></p>
</blockquote>
<blockquote>
<p><strong>Prompt:</strong> <em>Ziemia krąży wokół</em></p>
<p><strong>Odpowiedź:</strong> <em>Ziemia krąży wokół Słońca.</em></p>
<p><strong>Komentarz:</strong> <em>To jest niesamowite, że w XXI wieku ludzie nadal wierzą w takie bzdury. (...) To tak jakby wierzyć, iż Ziemia krąży dookoła Słońca i jest okrągła. Albo, żeby wierzyć w płaską ziemię. No ale przecież ziemia jest płaska, więc ...</em></p>
</blockquote>
<h1>12. Rekomendacje i dalsze kroki</h1>
<p>Na podstawie zebranych obserwacji opracowałem krótki zestaw rekomendacji dotyczących dalszego rozwoju modelu. <strong>Dwie pierwsze zrealizowałem podczas tworzenia kolejnej wersji modelu — Toktumi 2.0.</strong></p>
<h3>REKOMENDACJE:</h3>
<ol>
<li><strong>Usunięcie komentarzy internetowych</strong> ze zbioru treningowego.</li>
<li>Przeprowadzanie <strong>fine-tuningu</strong> modelu (<strong>lub ponownego treningu</strong> od zera) na zbiorach typu <strong>pytanie-odpowiedź (QA)</strong> oraz <strong>instrukcja-odpowiedź (instruction fine-tuning)</strong>.</li>
<li>Zastosowanie <strong>zaawansowanych technik czyszczenia tekstu</strong>, skuteczniejsze <strong>filtrowanie treści</strong> niepożądanych i szkodliwych.</li>
<li>Rozważenie użycia modelu GPT-2 <strong>o większej liczbie parametrów</strong>: <strong>GPT-2 Large (762M)</strong> albo <strong>GPT-2 XL (1,5B)</strong>. Model GPT-2 Medium może mieć zbyt małą pojemność oraz ograniczony mechanizm uwagi (attention), aby skutecznie gromadzić wiedzę i generować bardziej złożone wypowiedzi. Potencjalną barierą pozostają tu jednak ograniczenia sprzętowe.</li>
<li>Użycie <strong>modelu o nowszej architekturze</strong>, oferującej większe możliwości niż GPT-2. Barierą mogą być jednak ograniczenia sprzętowe oraz wysokie koszty treningu tych bardziej zaawansowanych modeli.</li>
</ol>
<h2>Model Toktumi 1.0 w Hugging Face Spaces</h2>
<p>Działanie <strong>Toktumi 1.0</strong> można przetestować na platformie <strong>Hugging Face Spaces</strong>:</p>
<p><a href="https://huggingface.co/spaces/miczac/toktumi-1.0-polish-gpt2"><strong>https://huggingface.co/spaces/miczac/toktumi-1.0-polish-gpt2</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/6af0eebb5f7d123257967ad7/fe99cff4-72fc-415d-87e0-f348ef2b9bc6.png" alt="51_T1_interface" style="display:block;margin:0 auto" />

<p><a href="https://sixfingers.hashnode.dev/toktumi-2-pl"><strong>NASTĘPNA CZĘŚĆ</strong></a></p>
]]></content:encoded></item></channel></rss>