Jest taki moment, kiedy model zwraca kod, plik się otwiera i przez dwie sekundy myślisz, że działa. Czasem faktycznie. A czasem karta przeglądarki zostaje pusta, albo wyskakuje biały ekran z błędem w konsoli. Ten sam model. To samo zadanie. Inna próba.
To jest historia o najnudniejszej i najważniejszej z cech generowanego kodu przez LLM: powtarzalności.
Zadanie: jedno HTML, sieć neuronowa
Do benchmarku, na którym sprawdzam modele do generowania kodu (https://benchmark.refio.dev/tasks/neuron_growth_simulation), dorzuciłem zadanie: napisz w jednym pliku HTML interaktywną symulację rozrostu sieci neuronowej. Canvas, prosta fizyka 2D, cząstki, kilka suwaków. Bez bibliotek, bez backendu – ma po prostu działać.
Przetestowałem na tym tasku 16 modeli, po sześć prób każdy – chmurowe (Claude, GPT, GLM) i lokalne, odpalane na własnej maszynie (Gemma, gpt-oss, Qwen w różnych rozmiarach). Każdy artefakt oceniam tym samym kryterium, które nazywam works out of the box: czy odpala się i robi to, co miało, bez ręcznych poprawek. Zero = nie rusza, połowa = działa, ale z bugami, jeden = działa.
Wyniki: nie „kto umie”, tylko „jak często mu wychodzi”
Czołówka i dół stawki (pełna tabela 16 modeli jest na https://benchmark.refio.dev/tasks/neuron_growth_simulation):
| Model | działa | bugi | nie rusza | wynik |
|---|---|---|---|---|
| Anthropic Sonnet 4.6 | 6 | 0 | 0 | 1,00 |
| GPT 5.1 codex mini | 6 | 0 | 0 | 1,00 |
| Anthropic Haiku 4.5 | 5 | 1 | 0 | 0,92 |
| … | ||||
| Qwen 3.5 27B | 1 | 2 | 3 | 0,33 |
| Gemma 4 26B | 1 | 1 | 4 | 0,25 |
| Gemma 4 31B | 1 | 0 | 5 | 0,17 |
(Qwen 3.5 9B remisuje z Gemmą 4 26B – 0,25; pełna szesnastka na benchmarku.)

Dwie rzeczy rzucają się w oczy.
Po pierwsze: tylko dwa modele zaliczyły komplet sześć na sześć – Sonnet 4.6 i GPT 5.1 codex mini. Cała czołówka to chmura i duże modele.
Po drugie, i to jest ciekawsze: żaden model nie miał zera na sześć. Nawet najsłabsza Gemma 4 31B, która pięć razy w ogóle nie ruszyła, raz napisała coś działającego. Czyli różnica między modelami nie jest „ten umie, tamten nie umie”. Każdy potrafi to napisać. Różnica jest w tym, ile razy na sześć mu się to udaje.
A to jest dokładnie ta cecha, której nie widać, kiedy odpalasz model raz, trafiasz na dobrą próbę i mówisz „działa, jest super”. Mały lokalny model, który robi działający kod raz na cztery podejścia, w demie wygląda identycznie jak ten, który robi go zawsze. Różnicę widać dopiero w skali – czyli w robocie.
Co znaczy „nie rusza”
Najczęstsze powody, dla których próba lądowała w kolumnie „nie rusza”, były prozaiczne:
- biały ekran – kod się ładuje, ale na canvasie nic się nie pojawia (literówka w selektorze, pętla, która nigdy nie startuje).
- błąd w konsoli na starcie – klasyczny
undefined is not a function, bo model odwołał się do czegoś, czego nie zdefiniował.
Nic egzotycznego. Po prostu kod, który wygląda jak kod, ale nie jest kodem, który działa.

Druga twarz porażki: kiedy kod nie pada, tylko zawiesza
Benchmark łapie próby, które się nie ładują albo mają bugi. Ale jest jeszcze jeden, paskudniejszy tryb porażki, którego sucha ocena „działa / nie działa” nie oddaje: kod, który się ładuje, rusza – i po kilku sekundach zawiesza kartę.
Symulacja rosnącej sieci to dla tego idealna pułapka. Animację robi się przez requestAnimationFrame, który woła sam siebie w pętli. Jeśli „rosnąca” sieć rośnie bez limitu, a rysowanie połączeń jest kwadratowe (każdy z każdym), to po kilkuset węzłach jedna klatka liczy się sekundy, pamięć puchnie, a przeglądarka idzie się powiesić – sama z siebie. Kod formalnie „działa”.
I nie jest to teoria. Otworzyłem jedną z prób gpt-oss 20B (lokalny model). Sercem symulacji jest funkcja update(), którą requestAnimationFrame woła jakieś 60 razy na sekundę. A w niej coś takiego:
// gpt-oss 20B, próba 02 - fragment update(), woła się co klatkę
finishedBranches.forEach(b => {
if (b.depth >= params.maxDepth) return;
if (Math.random() < params.splitChance) { // skończona gałąź losuje "podziel się"...
const child = new Branch(b.end, dir, b.depth + 1); // ...i robi to CO KLATKĘ, w nieskończoność
neurons.forEach(n => n.branches.push(child)); // a do tego dokleja tę samą gałąź do KAŻDEGO neuronu
}
});
// niżej, w tej samej klatce - wykrywanie synaps "każdy z każdym" po rosnącej liście:
for (let i = 0; i < finishedBranches.length; i++)
for (let j = i + 1; j < finishedBranches.length; j++)
if (blisko(finishedBranches[i], finishedBranches[j])
&& !synapses.some(s => ...)) // a w środku jeszcze przeszukanie wszystkich synaps
synapses.push(new Synapse(...));
Zobacz, co tu się dzieje. Skończona gałąź nigdy nie znika z finishedBranches. Więc co klatkę każda dotychczasowa gałąź na nowo rzuca kostką, czy się rozmnożyć – i te, którym wypadnie, dorzucają nowe gałęzie. Lista rośnie, a nowych dzieci doklejamy do wszystkich neuronów naraz (to akurat zwykły bug). Na to nakłada się pętla po synapsach: dwa zagnieżdżone for po tej samej puchnącej liście, a w środku jeszcze synapses.some(...). Przy stu gałęziach to dziesięć tysięcy porównań na klatkę. Przy tysiącu – milion. Co klatkę. Bez limitu węzłów, bez cancelAnimationFrame (sprawdziłem – nie ma go w żadnej z 96 prób).
Efekt jest dokładnie taki, jak na początku: ładuje się, przez chwilę ładnie rośnie, a po kilku sekundach jedna klatka liczy się ułamki sekundy, potem sekundy, RAM puchnie. Karta nie pada z błędem – po prostu przestaje odpowiadać a czasami i przeglądarka (niby jest izolacja procesów).
Najlepsze jest to, że jeden z modeli (Qwen 27B) napisał sobie w tym miejscu komentarz wprost: „For now we keep them to maintain the network structure” – „na razie trzymamy je, żeby zachować strukturę sieci”. Czyli sam zauważył, że nic nie kasuje, uznał to za ficzer i pojechał dalej. Podpisał się pod wyciekiem własnym komentarzem :)
Dla kontrastu Haiku, który to zadanie zaliczał prawie zawsze, robił prostą rzecz: split odpalał się raz, w momencie gdy gałąź kończy wzrost, i tylko dopóki depth < maxDepth. Drzewo się domyka, sieć jest skończona, pętla ma co rysować i nie zżera maszyny. Ta sama animacja, ta sama biblioteka do suwaków – różnica jest w jednym „kiedy”.
Wniosek
„Kompiluje się” to nie poprzeczka jakości – to podłoga. Poprzeczka to powtarzalnie działa. I to jest cała wartość robienia własnego benchmarku zamiast czytania cudzych tabelek: dopiero jak puścisz ten sam test sześć razy, widzisz, że najsłabszy model potrafi błysnąć, a najlepszego poznajesz po tym, że nie ma gorszych dni.
Modele sprawdzam na pluginie do InteliJ i CLI https://refio.dev/ – mój poligon do testowania lokalnych i chmurowych modeli na Agencie co raczej nie działa standartowo. Jeśli grzebiesz w podobnych rzeczach, napisz, chętnie pogadam.
0 komentarzy