Kera i praca obniżania

Kompilator to historia nieefektownej pracy między czystym grafem a prawdziwą maszyną: deterministyczne opuszczanie, własność celu i dowody, że jedno...

Kera i praca obniżania

The bug report that was not a bug

The story usually starts with a number that is nearly right. Not wildly wrong. Not broken in a way that makes the dashboard red. Nearly right in the expensive way: the sort of wrong that lets one team ship, another team hesitate, and an auditor ask why the answer changed when the same model moved from one machine to another.

One build runs on an x86 server and produces a value. The next build runs on an ARM laptop and produces a value close enough that ademo survives. A GPU path is faster but rounds through the computation differently. An FPGA path is attractive for timing but suddenly the software team is having a hardware conversation. Everyone can explain a small piece of the difference. Nobody owns the whole path from source to result.

That is the space Kera is built for. Not the model idea, not the marketing slide, not a paper about a future compiler, but the compiler work that begins once a team says the same computation must run on different machines andstill mean the same thing. That obligation needs an owner. The compiler promise is not that lowering is elegant. The promise is that lowering is treated as work.

Lowering is the part of a compiler where nice language intent becomes instructions that a target can execute. It is also where vague claims become visible. If the result is supposed to be bit for bit identical across CPU, GPU, FPGA, and WebAssembly, then the compiler cannot shrug at target differences. It has to carry enough structure to decide what maychange and what must not change. It has to know memory spaces, effects, opcodes, data movement, target limits, and the receipts that prove two builds are the same computation.

The tempting version of the story is to call this a portability layer. That is too small. Portability says the program runs elsewhere. Kera is aiming at the harder statement: the program is represented as a content-addressed graph, compiled to a .keg artifact, lowered to multiple backends, and still produces the same answer. The kitchen changes. The recipe does not.

The middle nobody wants to sell

The first conversation with a serious buyer rarely begins with syntax. It begins with a mess. There is a model that matters. There is a simulation that used to be research and has become operational. There is a risk calculation whose result is no longer allowed to vary by machine. There is an edge deployment that cannot carry the full cloud runtime. There is a team that wants an accelerator but cannot afford a rewrite every time the accelerator changes.

Most tooling makes this look like a deployment problem. Choose the target, export the model, patch the runtime, accept some variance, then write a document explaining the exceptions. The document grows. The test matrix grows. The number of specialists grows. Eventually the organisation is paying for heterogeneity twice: once when it buys the hardware, again when it tries to prove the hardware did the same work.

Kera starts from the opposite end. The Kera page calls it a statically typed systems language with a graph-native, content-addressed IR. That phrase matters because the graph is not a decorative compiler diagram. It is the executable object. Source code lowers into directed acyclic graphs of operations, stored as .keg files. Each node carries defined structure. Duplicate work can be eliminated structurally. The same graph can be lowered to CPU, GPU, FPGA, and WASM without treating every target as a separate little universe.

Dlatego Kera musi zachowywać się jak poważna infrastruktura kompilatora, a nie jak diagram. To niewygodne środkowe miejsce zamieszkuje klient. Tokenizacja musi zachowywać pozycje bajtów. Parsowanie musi zasilać narzędzia, a nie tylko kompilację. Sprawdzanie typów musi odrzucać niezgodności kształtu i krawędzi, zanim staną się zdarzeniami w czasie wykonania. Sprawdzanie pożyczek musi rozumieć własność w pamięci hosta, urządzenia, przypiętej i jednolitej. Optymalizacja musi być powtarzalna. Serializacja musi dać organizacji artefakt, który może zachować.

Żadne z tego nie tworzy dramatycznego nagłówka. Jest to o wiele bardziej przydatne niż nagłówek. Oznacza to, że gdy obciążenie przechodzi z rozwoju do produkcji, albo ze ścieżki CPU na ścieżkę GPU, kompilator ma stabilną rzecz do porównania: odcisk grafu. Jeśli odcisk się zgadza, praca jest tą samą pracą. To wspólny punkt kontrolny, który inżynier, kupujący i audytor mogą wszyscy zrozumieć.

Graf to paragon, nie obrazek

Stary sposób wyjaśniania programu to pokazanie źródła i poproszenie czytelnika, aby zaufał kompilatorowi. Strona Kera wciąż wraca do innego obiektu: grafu. Każda operacja to węzeł. Krawędzie wyrażają zależności. Graf ma treść. Treść ma skrót. Skrót staje się odciskiem, który podróżuje przez cały proces budowania.

Graf jest trwałym obiektem: źródło, typy, regiony, skróty i artefakt, który podróżuje.

To brzmi jak wewnętrzna mechanika, dopóki nie staniesz obok zespołu ds. zgodności. Zespół ds. zgodności nie chce wiedzieć, że dostawca ma ładną architekturę kompilatora. Chce wiedzieć, czy obliczenia zatwierdzone w zeszłym miesiącu to obliczenia uruchamiane dzisiaj. Odcisk grafu jest mostem między tymi światami. To nie zrzut ekranu kompilatora. To zwięzły sposób, aby powiedzieć: ten zestaw operacji, te typy, te wejścia, te atrybuty, ta struktura zależności.

Model węzłów jest celowo rygorystyczny. Węzeł to wersja schematu, kod operacji, deskryptory wyjściowe, skróty wejściowe i mapy atrybutów ułożone w kanonicznej kolejności. Bajty są haszowane algorytmem SHA3-256 pod prefiksami rozdzielonymi domenami, więc skróty węzła, regionu i grafu żyją w oddzielnych przestrzeniach nazw. Jeśli dwa węzły mają ten sam kod operacji, wejścia i atrybuty, mają ten sam skrót. Wstawienie duplikatu zwraca istniejący wpis. Eliminacja wspólnych podwyrażeń staje się konsekwencją reprezentacji, a nie heroicznym przebiegiem dodanym później.

To ten rodzaj szczegółu, który zamienia system inżynieryjny w coś, na czym ludzie mogą polegać. Kupujący nie kupuje diagramu kompilatora. Kupujący kupuje mniej kłótni o to, czy obliczenie się zmieniło. Deweloper nie kupuje hasła o deterministycznej sztucznej inteligencji. Deweloper kupuje format grafu, w którym identyczność można sprawdzić, przechowywać i używać przez narzędzia. Plik .keg nie jest zatem przemyślanym dodatkiem po fakcie. To przenośny zapis pracy.

Osiem małych drzwi, zanim pojawi się cel

Kiedy ludzie myślą o kompilatorze, często od razu przeskakują do backendu. Wyobrażają sobie moment, w którym kod staje się AVX, PTX, Verilog lub WASM. Ten moment jest ważny, ale pojawia się późno w tej historii. Większość pracy kompilatora została już wtedy wykonana, w cichych etapach, które decydują o tym, czy backendowi można zaufać.

Backendy pojawiają się późno. Większość zaufania buduje się wcześniej, w małych etapach kompilatora, które zachowują znaczenie.

Kera zaczyna się od leksera obsługującego UTF-8, który zamienia kod źródłowy na tokeny z pełnym śledzeniem trivia i odzyskiwaniem błędów. To brzmi jak elementy edytora, i tak właśnie jest. To także niezawodność całego toolchainu. Jeśli każda pozycja bajtu jest zachowana, diagnostyka, formatowanie, przyrostowe ponowne parsowanie i zachowanie serwera językowego mogą być zgodne z tym, co użytkownik faktycznie napisał. Kompilator, który wcześnie traci kształt źródła, płaci za tę stratę wszędzie indziej.

Parser jest oparty na zdarzeniach: rekurencyjne zejście z parsowaniem wyrażeń Pratta emituje zdarzenia Start, Token, Finish i Error, zamiast budować pojedyncze drzewo AST, które wszyscy muszą współdzielić. Strumień zdarzeń zasila niezależnie konstruktora drzewa, formater i serwer językowy. Zielone drzewo jest bezstratne, zachowując tokeny i trivia. Konstruktor grafu przechodzi następnie przez to drzewo, utrzymuje rozwiązywanie nazw i tworzy węzeł Region dla każdej funkcji.

Dopiero potem źródło staje się grafem, który kompilator przenosi. Strukturalne sprawdzanie typów weryfikuje zgodność na krawędziach operacji i ograniczeniach kształtu tensorów. Typy pasują do siebie tylko wtedy, gdy ich kanoniczne hashe są identyczne. Sprawdzacz pożyczania egzekwuje jednego właściciela na wartość, brak aliasowanych referencji modyfikowalnych oraz zasady własności w przestrzeniach pamięci. Adnotacje efektów są sprawdzane pod kątem spójności. Efekty uboczne nie są pozostawione gustowi ani konwencji.

Menedżer przebiegów optymalizacji wykonuje następnie rozpoznawalną pracę kompilatora: składanie stałych, eliminację wspólnych podwyrażeń, eliminację martwego kodu, inline'owanie, wektoryzację i fuzję pętli w pętli punktu stałego. Ważne zdanie to nie lista przebiegów. To, co dzieje się po każdym przebiegu: walidator niezmienników grafu sprawdza spójność hashy i integralność zależności. Optymalizacja może ulepszać graf, ale nie może czynić go tajemniczym.

Na koniec generatory kodu specyficzne dla celu emitują kod natywny, PTX, Verilog lub WASM, a zoptymalizowany graf jest serializowany do pliku .keg z tabelą sekcji. Plik zaczyna się od magicznych bajtów KEG\0 i zawiera sekcje dla ciągów, typów, atrybutów, węzłów, regionów i eksportów. To ścieżka, którą zespół musi przejść, zanim będzie mógł uczciwie mówić o celach.

Cele to nie naklejki na slajdzie

Lista celów jest łatwa do napisania i trudna do osiągnięcia. CPU, GPU, FPGA i WASM mieszczą się zgrabnie na karcie produktu. Obniżanie do nich to miejsce, gdzie ukrywa się rachunek. Każdy cel ma swoje własne nawyki, mocne strony i tryby awarii. Kompilator, który chce deterministycznego wykonywania heterogenicznego, nie może traktować tych nawyków jako cudzego problemu.

CPU, GPU, FPGA i WASM to nie etykiety. To światy docelowe, w których ten sam graf musi przetrwać.

Na procesorach CPU Kera generuje natywny kod maszynowy dla x86-64 i ARM64, a RISC-V Vector znajduje się w karcie technicznej. Ścieżka x86 wybiera SSE2, AVX2 lub AVX-512; ścieżka ARM używa NEON; alokator rejestrów i scheduler są współdzielone. Twierdzenie produktu nie sprowadza się do tego, że binarka istnieje. Twierdzenie jest takie, że jądra SIMD specyficzne dla danego targetu są wybierane, podczas gdy graf pozostaje tym samym obliczeniem.

Na GPU materiał źródłowy wymienia generowanie PTX dla CUDA i ROCm, z użyciem rdzeni tensorowych, gdy kształt jądra na to pozwala. To ostatnie zastrzeżenie robi tu robotę. Akceleracja sprzętowa to nie magiczny pył. Kształt jądra albo pasuje do ścieżki rdzeni tensorowych, albo nie. Kompilator musi to uczciwie ujawnić, przenosić dane między hostem a urządzeniem, gdy trzeba, i nadal utrzymywać odcisk grafu jako dowód tego, co jest wykonywane.

Na FPGA ścieżka loweringu staje się rozmową o sprzęcie: synteza Verilog, estymacja zasobów, analiza timingów i planowanie potoków dla przewidywalnego, cyklowo dokładnego zachowania. To rodzaj targetu, na którym machanie rękami jest szczególnie kosztowne. Jeśli kupujący potrzebuje twardego czasu rzeczywistego, powierzchnia kompilatora musi mówić w cyklach, zasobach i potokach, a nie tylko w prędkości. Historia Kery sprawia, że FPGA jest backendem tego samego grafu, a nie przepisaniem przez inny zespół.

Na WASM strona wymienia 128-bitowe SIMD dla wdrożeń brzegowych i przeglądarkowych. To ważne, ponieważ na brzegu rzeczywistość wdrożeniowa często zderza się z czystością. Przeglądarka, małe urządzenie lub ograniczone środowisko uruchomieniowe nie zawsze mogą pomieścić ten sam stos co serwer. Ścieżka loweringu natywna dla grafu daje zespołowi sposób na przeniesienie tego samego obliczenia do takiego środowiska bez zamieniania brzegu w drugi produkt.

Determinizm musi przetrwać sukces

Determinizm łatwo obiecać w małym pokoju. Trudniej, gdy produkt odnosi sukces. Przybywa więcej użytkowników. Pojawia się więcej sprzętu. Model przechodzi z jednego chipa na inny. Linia bazowa testów napisana dla jednego środowiska musi objąć kolejne. Optymalizacja, która wygląda nieszkodliwie, zmienia kolejność redukcji. Szybka ścieżka pojawia się w jednym środowisku uruchomieniowym, ale nie w innym.

Determinizm to pętla, nie slogan: buduj, uruchamiaj, porównuj, naprawiaj i utrzymuj graf stabilnym.

Strona Kera przedstawia determinizm jako bit po bicie na wszystkich platformach. To nie jest deklaracja kosmetyczna. Zmienia to obowiązki produktu. Arytmetyka prymitywów ma zdefiniowaną semantykę precyzji na wszystkich celach. Redukcje mają zdefiniowane rozstrzyganie remisów, więc planowanie i sprzęt nie zmieniają wyniku. Prymitywy sieci neuronowych mają deterministyczne implementacje na CPU, GPU, FPGA i WASM. Graf znaczy to samo niezależnie od tego, gdzie działa.

Dlatego niezdefiniowane zachowanie, pauzy garbage collectora i niejawne efekty uboczne to nie drobne preferencje językowe. To pęknięcia w powierzchni wykonania. Strona Kera mówi: brak zależności od LLVM, brak garbage collectora, brak niezdefiniowanego zachowania. Jawne typy, jawne efekty i własność w obrębie przestrzeni pamięci to mechanizmy kontrolne produktu. Zmniejszają liczbę miejsc, w których wynik może się zmienić, podczas gdy wszyscy patrzą gdzie indziej.

Jest też strona ludzka. Gdy kontroler robotyki zmienia zachowanie między urządzeniami, gdy przepływ finansowy zaokrągla jeden cent inaczej, gdy wynik naukowy nie może zostać powtórzony przez recenzenta albo gdy symulacja gry rozjeżdża się między celami, spór tak naprawdę nie dotyczy teorii kompilatorów. Chodzi o zaufanie instytucjonalne. Materiały konsumenckie Kera używają prostych historii, bo problem u podstaw jest prosty do odczucia: te same instrukcje, ta sama odpowiedź, na każdym komputerze, który posiadasz.

To zaufanie musi przetrwać przyspieszanie. Jeśli wydajność wymaga od zespołów porzucenia determinizmu, produkt tylko przeniósł ryzyko. Kera stara się utrzymać wydajność i determinizm w jednej umowie, czyniąc graf jednostką znaczenia, a ścieżkę docelową jednostką wykonania.

Bezpieczeństwo jest częścią loweringu

Dyskusje o kompilatorach często izolują bezpieczeństwo jako pracę w czasie wykonania. Materiały źródłowe Kera tego nie robią. Mówią o bezpieczeństwie opartym na zdolnościach, SecurityManagerze, bramie domyślnie odmawiającej, PolicyBuilderze, seccomp BPF, przestrzeniach nazw Linuksa i wątkowo bezpiecznym dzienniku audytu odpornym na manipulacje w buforze pierścieniowym o stałej pojemności. Te słowa należą do historii inżynierskiej, bo lowering nie kończy się w momencie wygenerowania instrukcji. Wygenerowana praca nadal musi działać w granicach.

Tokeny zdolności są przyznawane przy starcie. Działania uprzywilejowane wymagają jawnej zdolności. Reguły dostępu są deklarowane z góry. Filtr BPF ogranicza wywołania systemowe. Izolacja przestrzeni nazw rozdziela widoki procesu, montowania i sieci. Zdarzenia istotne dla bezpieczeństwa trafiają do dziennika audytu. Krótkie zdanie jest takie: nic nie jest dozwolone domyślnie.

To ma znaczenie dla wykonania heterogenicznego, bo cele tworzą powierzchnie. Proces CPU, transfer GPU, ścieżka FPGA, środowisko przeglądarki i zadanie rozproszone nie zawodzą w identyczny sposób. Produkt musi utrzymać politykę przywiązaną do pracy, gdy ta się przemieszcza. Jeśli graf mówi, czym jest obliczenie, mechanizmy kontrolne środowiska wykonania mówią, co obliczenie może zrobić.

Jest też powód operacyjny, by włączyć bezpieczeństwo do historii kompilatora. Zespoły nie chcą jednego produktu do kompilacji, drugiego do polityki, trzeciego do logowania i czwartego do wyjaśniania, jeśli szwy między nimi stają się miejscem, w którym chowają się incydenty. Strona Kera nie twierdzi, że rozwiązuje całe bezpieczeństwo. Robi coś węższego i bardziej użytecznego: czyni uprawnienia jawnymi i audytowanymi w środowisku wykonania, które zasila kompilator.

Praca rozproszona to nadal lowering

Karta techniczna wymienia wykonanie rozproszone, ring allreduce i odzyskiwanie po punktach kontrolnych. W innym produkcie mogłyby to być pozycje z broszury. W Kerze należą do tej samej rozmowy o loweringu, bo równoległość zmienia kształt obliczenia. Równoległość danych, modelu i potoku to nie tylko sposoby na przyspieszenie. To sposoby na podział pracy bez utraty znaczenia oryginalnego grafu.

Jeśli duże zadanie działa na wielu maszynach i jeden węzeł ulegnie awarii, odzyskiwanie danych z punktu kontrolnego nie jest wygodą. To część zapewniania operacyjności obliczeń. Jeśli wyniki są agregowane w sposób niezawodny, należy zdefiniować semantykę agregacji. Jeśli graf jest adresowany zawartością, ścieżka rozproszona musi zachować tożsamość grafu, a nie tworzyć drugą rzeczywistość, gdy zadanie opuszcza jedną maszynę.

W tym miejscu interpretacja produktu staje się jasna. Kera nie jest warstwą składni z rozproszonym dodatkiem. Stara się uczynić obliczenia przenośnymi zarówno pod względem kształtu, jak i sprzętu: jeden program, jeden graf, wiele celów wykonania oraz zapis, który można sprawdzić. Wykonywanie rozproszone to kolejne miejsce, w którym lowerowanie musi przenosić kontrakt wykonania.

Zmienia to także kwestię obsadzania zespołów. Bez wspólnej ścieżki kompilacji błąd pojawiający się tylko na jednej platformie może wymagać osób, które jednocześnie znają układ, toolchain i środowisko uruchomieniowe. Przy jednym grafie i wspólnej ścieżce pytanie jest ostrzejsze: czy zmienił się graf, czy zmieniła się ścieżka lowerowania, czy też cel naruszył zdefiniowaną semantykę? Lepsze pytania nie eliminują ciężkiej pracy. Powstrzymują ją przed rozprzestrzenianiem się w sposób przypadkowy.

Dlaczego ktoś musi być właścicielem ścieżki

Lowerowanie nie może pozostać bez właściciela, gdy niesie wyniki, na których ludzie polegają. Graf działający na CPU, GPU, FPGA i WASM potrzebuje czegoś więcej niż sprytnej reprezentacji. Potrzebuje dokumentacji, diagnostyki, deklaracji dotyczących celów, zachowania edytora, generowania kodu, kontroli bezpieczeństwa, rozmów wsparcia oraz sposobu, by powiedzieć kupującemu dokładnie, co się zmieniło, gdy zmienia się wynik.

To nie czyni pracy mniej techniczną. To czyni pracę techniczną bardziej wiążącą. Kera jest obecnie komercyjnym produktem Dweve, a nie wydaniem open source: wszystkie prawa zastrzeżone, dostępna na licencji komercyjnej, stworzona w Holandii i przeznaczona dla organizacji, które potrzebują wspieranej ścieżki grafu, a nie tylko opublikowanej. Jeśli strona mówi o Rust 2021, IR grafu, niestandardowym JIT, braku LLVM, CPU/GPU/FPGA/WASM, śledzeniu efektów, własności w różnych przestrzeniach pamięci, bezpieczeństwie opartym na możliwościach, wykonywaniu rozproszonym, osadzaniu w C i Pythonie, przepływach pracy CLI, wsparciu edytora i narzędziach LSP, to nie są dekoracyjne szczegóły wewnętrzne. To zobowiązania, które muszą przetrwać ocenę przez rzeczywiste obciążenia.

Karta możliwości jest w tej kwestii ostrożna. Opisuje cele projektowe i zaleca weryfikację względem własnego obciążenia. Ta powściągliwość ma znaczenie. Jest znacznie zdrowsza niż udawanie, że każda liczba z benchmarku się przenosi. System powinien dawać zespołom sposób na mierzenie, inspekcję i porównywanie we własnym środowisku, a nie prosić je o przyjęcie uniwersalnej historii o szybkości.

Ktoś musi być właścicielem tej ścieżki, ponieważ każdy cel stara się uczynić źródło mniej uniwersalnym. Kera to miejsce, w którym te różnice między celami stają się jawną pracą kompilatora, a nie folklorem przekazywanym po zgłoszeniu błędu.

Dzień, w którym graf staje się kontraktem

Wyobraź sobie ponownie oryginalne zgłoszenie błędu, ale z Kerą już w przepływie pracy. Zespół finansowy zatwierdza obliczenia ryzyka. Odcisk palca grafu jest rejestrowany. Pierwsze wdrożenie działa na CPU. Później wprowadzana jest ścieżka GPU do analizy portfela. Jeszcze później ścieżka FPGA jest używana do wyceny o niższym opóźnieniu. Pytanie na każdym etapie nie brzmi, czy nowy sprzęt robi wrażenie. Pytanie brzmi, czy ten sam graf jest lowerowany i czy semantyka celu utrzymuje identyczny wynik.

Rozmowa się zmienia. Zespół platformowy może rozmawiać o planowaniu i kosztach. Zespół kompilatora może rozmawiać o backendach. Zespół ds. zgodności może rozmawiać o odcisku palca. Właściciel biznesowy może zapytać, czy zmiana sprzętu jest decyzją operacyjną, czy przepisaniem. Produkt daje im wszystkim jeden obiekt, na który mogą wskazać.

To jest cicha wartość adresowalnego treścią IR. Zamienia wykonywanie międzyplatformowe z serii przekonujących wyjaśnień w zapis. Sprawia, że graf staje się pokwitowaniem. Pozwala temu samemu plikowi .keg zasilać każdy backend. Daje organizacji punkt odniesienia, który należy do obliczeń, a nie do pojedynczej maszyny.

Żaden kompilator nie usunie potrzeby inżynierskiego osądu. Zespoły nadal muszą starannie wybierać cele, uczciwie testować obciążenia, rozumieć ograniczenia celów i decydować, które domeny wymagają identyczności bit po bicie. Ale Kera może uczynić te decyzje jawnymi. Może zapobiec przypadkowej przemianie migracji wydajnościowej w migrację semantyczną.

Lekcja z obniżania poziomu

Kera ma znaczenie, ponieważ trudną częścią nie jest posiadanie sprytnego pomysłu na obniżanie poziomu. Trudną częścią jest przeniesienie tego pomysłu przez wszystkie nudne miejsca, w których inżynieria albo staje się godna zaufania, albo staje się folklorem: diagnostyka, zielone drzewa, hashe typów, sprawdzanie pożyczek, optymalizacja punktu stałego, walidacja niezmienników, serializacja .keg, emisja backendu, polityka bezpieczeństwa, dzienniki audytu, wsparcie edytorów i wykonywanie specyficzne dla celu.

Obniżanie poziomu to praca, ponieważ każdy cel stara się uczynić źródło mniej uniwersalnym. Dyscyplina deterministycznej inżynierii kompilatorów polega na odmowie pozwolenia, aby to działo się po cichu. CPU chce wektorów. GPU chce jąder. FPGA chce cykli. WASM chce ograniczeń. Organizacja chce jednej odpowiedzi. Zadaniem Kery jest zachowanie znaczenia obliczeń przy jednoczesnym pozwoleniu każdemu celowi na robienie tego, w czym jest dobry.

To jest kształt inżynieryjny, a nie papierowa abstrakcja. Ma nabywcę, tryb awarii, format pliku, ścieżkę kompilatora i konsekwencje operacyjne. Obietnica nie polega na tym, że sprzęt stanie się prosty. Obietnica polega na tym, że złożoność jest reprezentowana, obniżana, sprawdzana i posiadana.

Gdy pojawi się następna prawie poprawna liczba, zespół nie powinien zaczynać od folkloru o tym, która maszyna co uruchomiła. Powinien zacząć od grafu. Czy odcisk palca się zgadzał? Który backend wygenerował kod? Jakie możliwości zostały przyznane? Która semantyka celu została zastosowana? Który artefakt został przechowany? To są pytania produktowe. Kera istnieje, ponieważ są to również pytania kompilatorowe.