🇬🇧 English version: README_EN.md
bryla-kris
Własna reprezentacja semantyczna dla modeli językowych.
Bryła wozi gotowe znaczenie, żeby model mniej liczył od nowa.
Kim jestem
Krzysiek. Samouk. Nie jestem informatykiem ani programistą — uczę się tego wszystkiego po nocnych zmianach w pracy, sam, z pomocą AI. Zacząłem w grudniu 2025 od filmiku na YouTube, gdzie ktoś zbudował sobie asystenta Jarvisa. Pomyślałem: "chcę to mieć". I zaczęło się.
Szybko okazało się, że mój sprzęt (RTX 2060 12GB, Ryzen 5 3600, 32GB RAM) nie udźwignie dużych modeli. LLM-y ciągle przeliczały wszystko od nowa, kontekst się gubił, GPU mieliło. Frustrowało mnie to. Zamiast czekać na lepszy sprzęt, zacząłem szukać innej drogi.
I tak powstały bryły.
Co to jest bryła
Bryła to wielościenny obiekt semantyczny — paczka, która niesie ze sobą nie tylko tekst, ale też prekomputowane znaczenie: emocje, ważność, relacje, kontekst, stan. Zamiast zmuszać model do odgadywania tych rzeczy za każdym razem od nowa, parser rozkłada zdanie na bryły i każda bryła mówi modelowi: "oto co wiem o sobie".
Pomysł jest prosty: bogate wejście + mały model = tańsze uczenie niż duży model + surowy tekst.
Przykład
Zdanie: "UWAZAJ! To jest niebezpieczne!"
Zwykły LLM dostaje surowy tekst i musi sam zgadnąć, że to jest alarm.
Mój parser rozkłada to na bryłę z 20 tokenami metadanych:
[ENTITY] UWAZAJ [POL:neutral] [SCOPE:session] [COLOR:red] [URG:immediate]
[INT:high] [ATT:high] [CORE] [SRC:parsed] [COMPL:high] [NEG:none]
[INTENT:demand] [TOPIC:new] [DENSE:low] [DEP:deeply_dependent]
[EXPECT:description] [READY]
Model nie musi zgadywać. Widzi [COLOR:red] — alarm. [URG:immediate] — natychmiast. [INTENT:demand] — żądanie. [CORE] — to jest jądro zdania. Wszystko prekomputowane przez parser, podane na talerzu.
Jak to się rozwijało
Pole z 36 punktami (grudzień 2025)
Pierwsza próba. 32 litery polskiego alfabetu + znaki dodatkowe, ułożone jako punkty na płaskim polu. Naiwne, nie działało dobrze, ale dało mi intuicję, że geometryczna reprezentacja tekstu to coś innego niż tokenizacja.
Kostka sześcienna (styczeń 2026)
Sześć ścian, każda niosąca inną warstwę informacji. Już lepiej — model dostawał strukturę zamiast płaskiego stringa. Ale za mało wymiarów.
Bryły v2–v5 (luty–marzec 2026)
Przejście od kostki do wielościanu. Coraz więcej ścian, coraz więcej pól. Walidatory, pipeline'y, benchmarki. Z każdą iteracją coś się psuło, coś się naprawiało, coś stawało się lepsze. Pięć wersji, każda lepsza od poprzedniej.
Bryły v6 + GRU seq2seq (marzec 2026)
Pierwszy model, który naprawdę mówił. 9.4M parametrów, GRU z Bahdanau attention, val_ppl spadło do 7.3. Model generował zdania po polsku z drobnymi błędami. Dowód, że bryły działają.
Bryły v7 + Transformer (kwiecień 2026)
Przeskok na architekturę Transformer encoder-decoder (6+6 warstw, 53.5M parametrów). val_ppl = 31.06. Model odpowiadał sensownie na pytania o Jarvisa, relacje, pamięć. Odpowiedzi typu: "relacja modifies określa kierunek działania", "pamięć robocza utrzymuje i manipuluje informacją [...] model Baddeley'a".
Bryły v8 + AFFECT (kwiecień 2026)
Dodanie ściany afektywnej: kolor emocji (green/yellow/orange/red), pilność, intensywność, attention_weight, is_core. val_ppl spadło z 31 do 24.12 — poprawa o 22%, bez zmiany architektury modelu, bez zmiany korpusu. Jedyna różnica: bogatsze wejście. To potwierdziło hipotezę, że bryły pomagają.
Bryły v9 + Platforma (kwiecień 2026)
Refaktor na platformę schema-driven. Cała logika parsera i serializera napędzana plikami JSON zamiast hardkodu. Dodanie warstwy pragmatycznej (negacja, siła intencji, kontynuacja tematu, gęstość informacji, oczekiwany format odpowiedzi), source_type (skąd pochodzi informacja), completeness, weighted loss w treningu. 89 tokenów specjalnych, 20 metadanych per bryła. val_ppl = 24.02 — najlepszy wynik w historii projektu.
Wyniki
| Wersja | Architektura | Parametry | val_ppl | Opis |
|---|---|---|---|---|
| v6 | GRU seq2seq + attention | 9.4M | 7.3 | Pierwszy działający model |
| v7 | Transformer 6+6 | 53.5M | 31.06 | Brak affect, surowe bryły |
| v8 | Transformer 6+6 | 53.5M | 24.12 | + AFFECT/IS_CORE/ANCHOR |
| v9 | Transformer 6+6 | 53.6M | 24.02 | + platforma + pragmatyki + weighted loss |
Spadek val_ppl z 31 do 24 to 22% poprawy uzyskane wyłącznie przez wzbogacenie wejścia — ten sam model, ten sam korpus, te same hiperparametry.
Aktualizacja: pomiary v11/v12 (czerwiec 2026) Po publikacji wersji v9 przeprowadziłem znacznie dokładniejsze pomiary, które dały twardsze dowody niż samo val_ppl — i jednocześnie zmusiły mnie do skorygowania wcześniejszych wniosków. Opisuję oba, bo uczciwość pomiaru jest dla mnie ważniejsza niż ładny wynik. Co się okazało z val_ppl z v8 Wcześniej pisałem, że spadek val_ppl z 31 do 24 (22%) "potwierdził, że bryły pomagają". Z dzisiejszą wiedzą jestem ostrożniejszy: ten pomiar był na korpusie, który — jak się później okazało — krzywdził bryłę. Sprawdziłem to liczbowo: w moim korpusie było 9080 unikalnych tekstów (87%), ale tylko 483 unikalne bryły (5%). Bryła widziała 20× mniej zróżnicowania niż surowy tekst. To znaczy, że stare pomiary niedoszacowały bryły — remis osiągała MIMO ogromnego handicapu danych. Kontrolowany test na zróżnicowanym zestawie Zbudowałem generator zdań, w którym znam prawdę z góry (zdanie powstaje Z wartości ścian, więc tekst i ściana niosą tę samą informację, a wartości są zbalansowane). Na takim zestawie przepuściłem pełną siatkę konfiguracji: liczba grup {1, 3, 8} × rozmiar modelu {32, 64, 128} × szum parsera {0, 10, 20%} = 27 kombinacji. Wynik: bryła wygrała z bazą tekstową w 24 z 27 konfiguracji. szum parsera bryła wygrała jakość bryła vs baza przewaga 0% 9/9 100,0% vs 95,0% +5,0 pp 10% 9/9 98,3% vs 95,5% +2,8 pp 20% 6/9 97,2% vs 96,3% +0,8 pp Trzy przegrane wystąpiły wyłącznie przy najmniejszym modelu (dm=32) połączonym z najwyższym szumem (20%). Przyczyna: zbyt mały model nie ma pojemności, by nauczyć się ignorować błędnie otagowane ściany i ratować się tekstem. Przy modelu dm≥64 bryła wygrywała nawet przy 20% szumu. Wniosek: im gorszy parser, tym większy model potrzebny. Szybkość i rozmiar W osobnych pomiarach (rosnący rozmiar modelu) bryła okazała się szybsza w treningu przy większej skali — do ~2,96× przy d=512, z trendem rosnącym — oraz mniejsza parametrycznie (przewaga maleje ze skalą, od ~12× do ~1,2×). Gdy model rośnie 10×, koszt bazy rośnie ~6,3×, a koszt bryły ~2,2×. Uczciwe zastrzeżenie Zróżnicowany zestaw jest syntetyczny — ściany są w nim czystym sygnałem. Bryła wygrywa, bo czyta gotowe ściany, podczas gdy baza musi je wydobyć z tekstu. To jest mocny dowód koncepcji (architektura jest wierna i odporna na szum), ale nie jest jeszcze dowodem na naturalnym języku, gdzie realny parser może mieć błędy systematyczne, nie losowe. Następny krok to test na prawdziwym, nie generowanym korpusie. Czego się nauczyłem Najważniejsza lekcja nie dotyczy architektury, tylko danych: bryła nie przegrywała z powodu budowy, tylko dlatego, że korpus dawał jej 5% zróżnicowania. Problemem nie był model — był zestaw treningowy. To samo czeka rozszerzenie multimodalne: bez zróżnicowanych, czystych danych nie da się uczciwie zmierzyć przewagi.
Update: control experiments — and a correction to the 24/27 claim After sharing the 24/27 result, I ran the control ladder suggested on the HF forum (thank you @John6666). The controls changed my conclusion, so I'm documenting the full path here — including where the earlier claim was too strong. I'd rather show the correction than hide it. Why 24/27 was partly an artifact In that grid the model predicted the SAME walls it received as input. So a large part of the score came from the model copying the value set from input to output, not from understanding structure. The control test made this visible: RAW 96.8% DOMAIN 96.3% BRYLA 100.0% SHUFFLED 99.9% <- shuffling wall assignment barely hurt RANDOM 96.2% BRYLA ≈ SHUFFLED means the wall assignment wasn't being used — only the bag of values. The earlier benchmark measured reconstruction more than meaning. So I no longer claim "24/27 proves structure helps." It proves the model can reconstruct a value set it was given. Diagnosis The values were self-identifying: each value belonged to only one wall ("formal" only in REG, "negative" only in POL). The model never needed the wall labels — the value revealed its wall. A bag of values was enough, so shuffling labels didn't hurt. Redesigned test (inference, not copying) Two changes: (1) predict a HIDDEN wall not present in the input, so the model must infer; (2) make all walls share the same value space (A/B/C), so a value no longer reveals its wall — the model MUST use the assignment. Target rule requires distinguishing one wall from another. Text carries only a neutral topic, never the target. Result (2700 pairs, balanced, with and without text — nearly identical): chance RAW BRYLA SHUFFLED RANDOM w/ text 66.7% 68.6% 100% 79.0% 66.4% no text 66.7% 68.6% 100% 81.0% 68.6% Now SHUFFLED collapses ~20 points below BRYLA. RANDOM and RAW sit at chance. The gain comes from real values in their correct wall positions — not from prefix format, not from text. Honest conclusion (conditional) Putting both tests together: unique values per wall -> SHUFFLED = BRYLA (structure redundant) shared values per wall -> SHUFFLED < BRYLA (structure necessary) Wall structure carries information exactly when values are not self- identifying. Overlapping values are the natural-language case ("high" can be certainty, urgency, or intensity; "not" can be negation or part of another construction). My first synthetic set was too clean, which hid the value of structure. What this does and doesn't show Does: the wall structure (labels + assignment) carries information a bag of values does not, under overlapping values. This justifies the structure, not just the presence of features. Doesn't: it's still synthetic. It shows structure CAN matter and WHEN — not that my parser on real Polish text produces enough value-overlap for structure to help in practice. That's the next step. Reproduce it yourself The scripts are in this repo — don't take my word, run them: python kontrole.py --pairs dataset_zroznicowany.json --epoki 30 --dmodel 64 python test_wspolne_wartosci.py --epoki 30 --dmodel 64 --na-kombinacje 100 Raw results: kontrole_wynikow.csv, wspolne_wynikow.csv
Ściany bryły v9
Każda bryła niesie informację na wielu warstwach:
Semantyka — typ bryły (action/entity/role/qualifier...), intencja, rola, fokus semantyczny, poziom abstrakcji
Affect — kolor emocji (green→red), pilność (none/soon/immediate), intensywność (0-1), waga uwagi, waga uczenia (red = 2.5x częściej trenowane)
Pragmatyka — zasięg negacji, siła intencji (weak→demand), kontynuacja tematu, gęstość informacji, głębokość zależności, oczekiwany format odpowiedzi
Relacje — graf powiązań między bryłami (acts_on, qualifies, anchored_by, sequence_next...)
Stan — pewność parsera, kompletność, gotowość, źródło informacji (parsed/verified/stated/retrieved)
Kotwice — co N brył parser wstawia bryłę-kotwicę z kompresją kontekstu (atakuje problem lost-in-the-middle)
Temporal (schemat gotowy, aktywacja w v10) — oś czasu, świeżość, porządek zdarzeń
Speaker (schemat gotowy, aktywacja w v11) — profil rozmówcy, rejestr językowy
Sensory (placeholder na audio/foto) — sloty na embedding audio (Whisper) i obrazu (CLIP), gotowe do podłączenia
Problemy LLM które bryły atakują
| Problem | Jak bryły to atakują |
|---|---|
| Halucynacje | [SRC:parsed/verified/inferred] — model widzi skąd pochodzi fakt |
| "Nie wiem" | [COMPL:low/med/high] — model widzi czy ma dość danych |
| Ważne vs nieważne | [CORE] + [ATT:high] + kotwice |
| Lost in the middle | Kotwice pamięciowe co N brył |
| Ironia i ton | [COLOR:red] + [URG:immediate] + [INT:high] |
| Koszt obliczeń | Prekomputowane znaczenie = model mniej liczy od nowa |
| Catastrophic forgetting | Specjalizacja przez zmianę schematu, nie modelu |
| Brak kontroli generowania | [HINT:resolve/expand/amplify] + [EXPECT:plan/number/yesno] |
Architektura platformy v9
Warstwa 3: DOMENA (specjalista)
schema_jarvis.json / schema_kucharz.json / schema_medical.json
↕
Warstwa 2: SILNIK (parser_engine + serializer_engine + train)
napędzany schematem JSON — zero hardkodu
↕
Warstwa 1: FUNDAMENT (schema_base.json)
89 tokenów specjalnych, 10 ścian bryły, kotwice, affect, pragmatyka
Zmiana domeny = zmiana jednego pliku JSON. Zero zmian w kodzie.
Cel strategiczny
NIE buduję kolejnego chatbota. Buduję architekturę uczenia, która pozwala tanio trenować specjalistę w dowolnej domenie — medycyna, kuchnia, prawo, robotyka — byle korpus był dobry.
Hipoteza: jeśli bryły niosą prekomputowane znaczenie na wielu ścianach, to mały model (53M params) + bogate bryły daje wynik porównywalny z dużym modelem + surowy tekst, ale znacznie taniej.
v8 potwierdziła tę hipotezę na poziomie tokenów: 22% poprawy val_ppl bez zmiany modelu.
Sprzęt
Cały projekt trenowany na domowym sprzęcie:
- GPU: NVIDIA RTX 2060 12GB
- CPU: AMD Ryzen 5 3600
- RAM: 32GB
- OS: Windows 11 Pro
- Czas treningu: ~20 minut na 40 epok
Nie mam datacenter. Nie mam A100. Mam zwykłego peceta i upór.
Jak uruchomić
# Wymagania
pip install torch
# Przygotowanie danych
python prepare_data_v9.py \
--base schema_base.json \
--domain schema_jarvis.json \
--corpus_dir korpus/ \
--output pairs_v9.jsonl
# Trening
python train_v9.py \
--base schema_base.json \
--domain schema_jarvis.json \
--dataset pairs_v9.jsonl \
--workdir workspace_v9/ \
--epochs 40 --fp16
Co to nie jest
- Nie jest konkurentem GPT-4. To jest eksperyment na małym modelu, z własną architekturą wejściową, na domowym sprzęcie.
- Nie jest skończone. To jest praca w toku. Wersja 9 z wielu, które jeszcze będą.
- Nie jest doskonałe. Korpus ma 1.92 MB, model czasem powtarza słowa, chain-of-thought zatruwanie z danych jeszcze nie usunięte.
Ale działa. I z każdą wersją działa lepiej.
Plan dalszego rozwoju
- v10 — bryła jako jednostka uwagi (hierarchical attention między bryłami zamiast tokenami — potencjalnie 20-50x mniej obliczeń)
- v11 — generowanie brył zamiast tekstu (model produkuje bryły, nie słowa — otwiera multimodalność)
- v12 — schemat brył jako platforma dla wielu dziedzin (medycyna, kuchnia, robotyka — ten sam silnik, inne bryły)
- Sensory — podłączenie audio (Whisper) i obrazu (CLIP) jako ścian bryły
Skąd to się wzięło
Siedziałem w domu, RTX 2060 mieliła Qwena, kontekst się gubił, frustracja rosła. Zamiast narzekać, zacząłem kombinować: a co jeśli zamiast zmuszać model do przeliczania wszystkiego od nowa, zapakuję część znaczenia w samą wiadomość? Najpierw było pole z 36 punktami. Potem kostka. Potem bryły. Potem ściany afektu. Potem platforma.
Nie wiedziałem, że to się nazywa "neurosymbolic AI". Nie wiedziałem, że ludzie od lat badają "structured inputs". Doszedłem do tego sam, bo mnie bolało, że LLM ciągle liczy to samo od nowa.
Bryła nie generuje znaczenia — ona je przenosi. Z parsera do modelu. To jest jak lampa nad stołem: rozświetla raz, nie za każdym razem.
Licencja
Możesz używać, modyfikować i dzielić się — ale nie komercyjnie, z podaniem autora i na tej samej licencji.
Kontakt
Jeśli robisz coś podobnego, jeśli masz pomysł jak to rozwinąć, jeśli borykasz się z tymi samymi problemami na słabym sprzęcie — pisz. Szukam ludzi do współpracy, nie konkurencji.
HuggingFace: krzysiekpl
Projekt rozwijany od grudnia 2025. Sam. Po nockach. Z pasją i uporem.