•  

    Głupi programiści, prawo Moore'a, prędkość światła, 1024 rdzenie i to cholerne #programowanie funkcyjne
    (Nie wiem czy jest tu duże zapotrzebowanie na takie dłuższe, mniej typowe wpisy -- sprawdźmy!)

    Co nas ratowało, gdy w ciągu dziesiątek lat pisaliśmy coraz bardziej skomplikowane programy, coraz bardziej na odpieprz, coraz mniej przejmując się wydajnością?

    Kolesie projektujący hardware. Prawo Moore'a. Liczba tranzystorów na małym czipie rosła wykładniczo, a wraz z nią częstotliwość pracy procesora. Nie musieliśmy robić nic, by ten sam program chodził ze 2x szybciej na sprzęcie o rok-dwa lata nowszym, bo nowy procek w każdej sekundzie wykonywał 2x więcej instrukcji.

    Ale kolesie od hardware'u już nie mogą już nas ratować. Są limity częstotliwości. Prędkość światła. Szerokość ścieżek przewodzących. Już teraz mają ile -- kilka atomów? Kilkadziesiąt?

    Zegary procesorów praktycznie stanęły w miejscu, ale za to przybywa nam rdzeni. Dzisiaj w maszynach developerskich 4 to normalka, a da się kupić i 8.

    Jak dziwnie by to nie brzmiało, wygląda na to, że w przyszłości zaliczymy nie tylko 16 rdzeni, ale i 512, i 1024 i więcej. Dziesiątki tysięcy rdzeni! Podobne rzeczy dzieją się już teraz na rynku kart graficznych.

    100 rdzeni. 999 nieużywanych
    Czy zwykły kod może zyskać na szybkości gdy odpalimy go na 1000 rdzeniach? Np. taka pętla for:

    for (i = 0; i < MILION; i++) {
    zrobcos(i);
    }

    Skoro mamy odpalić zrobcos() milion razy, każdy rdzeń mógłby -- równolegle -- zrobić to po 1000x. I mielibyśmy praktycznie tysiąckrotne przyspieszenie.

    Ale skąd mamy wiedzieć, czy wolno nam wywoływać równolegle zrobcos()? A może funkcja ta modyfikuje jakiś stan globalny? Może kolejne jej wywołanie bazuje na poprzednim? Mogłaby przecież dodawać otrzymany parametr do jakiejś listy. Zrównoleglenie wywołań oznaczyłoby praktycznie przypadkową kolejność wykonywania funkcji, a więc zaburzyłoby zawartość listy.

    Są też niezliczone inne sposoby, w które nawet ledwie dwa wątki, wywołujące równolegle zrobcos(), psułyby sobie nawzajem sytuację.

    W ogólności więc, taka pętla musi się wykonywać szeregowo. Na jednym rdzeniu. Pozostałe 999 może sobie spać.

    Aby to naprawić, obecnie stosujemy zwykle jedną technikę.

    Ręcznie synchronizowany kod współbieżny
    Mówimy maszynie, że ten fragment wydaje nam się bezpieczny i że można go zrównoleglić. Ale na ilu wątkach? Już przy dwóch mogą wystąpić zakleszczenia. Jeden wątek może nadpisać drugiemu współdzielone dane. W kolejności, której nie przewidzieliśmy. W dowolnej instrukcji. W dowolnej kombinacji.

    Ręczne żonglowanie trzema czy czterema wątkami jest jeszcze bardziej karkołomne.

    Stosujemy więc synchronizację, semafory i inne narzędzia.

    Niestety, współbieżne wykonanie jest chaotyczne. Nigdy nie wiadomo, co się dokładnie kiedy wykona i w jakiej kolejności. Trudno więc tu wykryć błąd. Możemy w 100% pokryć test automatycznymi testami. Możemy go wykonać na 10 różnych maszynach po 100x. Bezbłędnie.

    W gotowym produkcie, w tysiąc pierwszym odpaleniu, nasz kod się wyłoży. A my nie będziemy w stanie tego odtworzyć.

    Dlatego programowanie współbieżne jest tak szalenie trudne. Gdy ktoś nam mówi, że wcale nie -- najprawdopodobniej znaczy to, że sam nawet nie zdaje sobie sprawy z jego trudności.

    Takie problemy mamy już przy 2 czy 4 wątkach. A przy 1024? Przy 64k?

    Niby jak mamy wykorzystać dziesiątki tysięcy rdzeni, które kolesie od hardware'u dadzą nam za 20 lat, skoro nie ogarniamy zaledwie kilku?

    Okazuje się, że jest sposób programowania, które jest z definicji bezpieczne dla wielu wątków. Więcej: dla dowolnej ilości wątków.

    Programowanie funkcyjne
    Tak, używa się w nim funkcji. Można tworzyć funkcje anonimowe, dostawać funkcje jako argumenty i zwracać funkcje z innych funkcji.

    Ale dla współbieżności, kluczowa jest w zasadzie tylko jedna dyscyplina.

    Musimy pozbyć się... operatora przypisania.

    Tak. Operatora przypisania. Nie można używać "=". No OK, można sobie go użyć by nadać zmiennej wartość początkową, ale potem tej wartości nie można już zmieniać. Więc w sumie mamy nie tyle zmienne, co stałe. Nie można też używać rzeczy takich jak i++, bo to tak naprawdę tyle co i=i+1, a więc znowu operator przypisania.

    Miejsca w pamięci mogą zostać zainicjalizowane wartościami, ale te wartości muszą tam potem zostać. Nie mogą się zmienić.

    Co nam to da?

    To, że nawet jeśli nasz kod będzie wykonywał się na milionie wątków, żaden nie nadpisze danych drugiego. Bo w ogóle żaden nic nie będzie "nadpisywał".

    Czy da się tak w ogóle pisać? Dane to dane, ale jak np. pisać głupie pętle bez zmiennych sterujących?

    Okazuje się, że się da. I ludzie piszą tak w językach funkcyjnych, takich jak Clojure, OCaml czy Erlang.

    Pętle? Kolekcje, które możemy modyfikować?
    Tutaj nie używa się pętli. Używa się rekurencji.

    Myślisz, że to powolne? Że wywali stos? Nie. Te języki mają optymalizację rekurencji ogonowej. Jeśli napisze się rekurencję poprawnie, interpreter będzie wykonywał nasz kod "w miejscu", bez spamowania niepotrzebnymi ramkami stosu wywołań. Szybkie i bezpieczne.

    A co z listami, tablicami, mapami? Często przecież piszemy kod, który dodaje coś do listy.

    Tu, w językach czysto funkcyjnych, listy są stałe. O tak: masz funkcje, które wstawiają coś np. na początek listy. Ale to nie tak, że one modyfikują jakąś listę. One zwracają nową listę, z dodatkowym elementem z przodu.

    Myślisz, że to powolne i strasznie pamięciożerne? Nie. Te języki optymalizują listy. Kojarzysz listy wiązane, gdzie jeden element ma referencję do następnego? Wyobraź sobie taką listę:

    C->B->A

    Wyobraź sobie, że dodajesz kolejny element na jej początek:

    D->C->B->A

    Widzisz jakąś prawidłowość? Tak: stara lista zawiera się w nowej. Nowa lista to po prostu referencja do elementu D, którego referencja następnego elementu wskazuje na element C, a więc na de facto poprzednią listę. Nic nie jest kopiowane. Nic się nie marnuje.

    W językach funkcyjnych, funkcje nie mają efektów ubocznych. Załóżmy, że masz funkcję zrobcos(i). Oraz że gdy przekażesz jej parametr 42, to ona zwróci ciąg "foobar". Co zwróci, gdy wywołasz ją drugi raz z takim samym parametrem? Zwróci tę samą wartość: "foobar". To gwarantowane. I po raz trzeci. I po raz tysięczny. I za żadnym razem funkcja nie zrobi niczego innego poza zwróceniem ciągu znaków. Niczego nigdzie indziej nie popsuje.

    Nie ma ukrytego stanu. Nie ma efektów ubocznych. Teoretycznie -- a stopniowo coraz praktyczniej -- daje to potężne możliwości.

    Nie dzieje się nic ciekawego i mamy akurat 500 wolnych rdzeni?

    No to wykorzystajmy je, by odpalić funkcję zrobcos() dla wartości od MILION do DWA_MILIJONY. I żeby zapamiętać wyniki. Na przyszłość. Gdy ktoś o nie poprosi -- już je będziemy mieli.

    Ktoś nas poprosił o silnię z 5, a mamy jeszcze 5 wolnych rdzeni? Policzmy silnię do 10, na wszelki wypadek, i zapamiętajmy wyniki na potrzebę przyszłych wywołań.

    Wracając do początku...
    Co z tą pętlą po milionie elementów listy? Implementujemy ją trochę inaczej. Odpalamy funkcję MAP, której przekazujemy listę miliona liczb ([0..MILION)) i funkcję F, która jest fragmentem kodu, który chcemy wywołać dla każdego elementu listy. Wygląda to jakoś tak:

    MAP(F, RANGE(0, 1000000))

    Choć w językach funkcyjnych obowiązuje często składnia z LISP-a, w której nie trzeba stawiać przecinków pomiędzy argumentami, a nawiasy otwierające stawia się w złym miejscu (przed nazwą funkcji, a nie po niej):

    (MAP F (RANGE 0 1000000))

    Funkcja F zostanie wywołana dokładnie raz dla każdego elementu, i dostanie ten element -- tj. liczbę -- za argument. Ponieważ F nie może mieć efektów ubocznych, jedynym sposobem, żebyśmy coś tu w ogóle zrobili, jest zwrócenie wartości przez funkcję F. Funkcja MAP zwróci nową listę o milionie elementów, z czego i-ty element będzie zawierał wartość funkcji F(i).

    Co nam to daje? Skoro F nie ma efektów ubocznych, nie trzeba jej nawet wykonywać w dobrej kolejności. Nie ma "dobrej kolejności". F(1) zwróci jakąś wartość X, a F(123) zwróci Y niezależnie od tego, w jakiej kolejności zostaną wykonane.

    Środowisko wykonawcze może więc ją odpalać równolegle na 1000 rdzeniach. Czyli dla 1000 elementów naraz. A jak nam dołożą drugi tysiąc rdzeni, to dla 2000.

    Programista już się o to nie musi martwić.

    Musiał się tylko martwić o to, jak u licha napisać kod bez instrukcji przypisania.

    pokaż spoiler "Trochę" (sporo) tu naupraszczałem, ale post i tak już się zrobił długi. Przepraszam za to wszystkich, szczególnie bardziej obeznanych w temacie. Mam jednak nadzieję, że w istotnych tutaj kwestiach, post powinien być "wystarczająco prawdziwą aproksymacją".
    pokaż całość

  •  

    Prawdziwe przerażenie. Poczuliście je kiedyś, gdy zaledwie usłyszeliście o jakiejś hipotetycznej, choć prawdopodobnej sytuacji? Gdy dotarły do Was jej konsekwencje?

    Mi się to niedawno zdarzyło.

    Nie -- nie chodzi o teorie spiskowe. To było coś, co nie na żadnego związku ze spiskiem jakiegokolwiek rządu czy organizacji, ani zagrożeniem ze strony kosmitów. To byłyby myśli nienaukowe, niepoparte trendami. Spekulacje.

    Myśl, o której mówię, zaczyna docierać do coraz większej liczby wpływowych ludzi, którzy mają pewną wiedzę na ten temat. Elona Musk. Billa Gates. Pojawia się na ten temat coraz więcej dyskusji, ale wciąż nie w mainstreamie..

    Oto ta myśl.

    Zostaniemy pokonani. Nie będziemy w stanie absolutnie nic z tym zrobić. Zostaniemy zmiażdżeni. Zdeklasowani przewagą tak ilościową, jak i jakościową. Całkowicie zdani na coś, co wobec nas pozostanie całkowicie bezduszne. Jeśli w ogóle dopuścimy do tego, żeby zaczęła się walka na większą skalę -- przegramy ją z kretesem. Na pewno.

    Czy taka walka będzie miała miejsce? Prawdopodobnie, jeśli nic z tym nie zrobimy.

    Co nas zniszczy?

    AI. Sztuczna inteligencja.

    Zdaje się rodzić w bólach i, podobnie jak latające samochody czy statki kosmiczne, zdaje się być w tyle za filmami science-fiction. Nawet tymi sprzed 50 lat. Ale to niesprawiedliwe porównanie.

    Aby to zrozumieć, przyjrzyjmy się podstawowemu podziałowi AI. Wyróźnijmy trzy poziomy.

    1. AI, która potrafi skutecznie wykonywać jedną, konkretną, potencjalnie złożoną czynność. Równie dobrze jak człowiek. Albo lepiej.
    2. AI, która jest wszechstronna i dorównuje pod każdym względem człowiekowi. Może uczyć się nowych rzeczy i robić wszystko równie dobrze jak człowiek.
    3. Super AI, czyli taka, która jest inteligentniejsza od każdego człowieka.

    Na którym poziomie są AI, które obecnie tworzymy?

    Na pewno nie na 2 i 3. W filmach często mamy do czynienia z poziomem 2, ale to dlatego, że bardzo łatwo stworzyć obraz takiej inteligencji. Nie trzeba nawet efektów specjalnych. To po prostu umysł scenarzysty. Poziom 3 nieco trudniej pokazać, bo autor fikcji nie wymyśli przecież niczego, czego żaden człowiek nie mógłby wymyślić. Może jednak np. podać, że jego AI w ułamek sekundy stworzyła lek na HIV i sposób na zimną fuzję.

    A co z wyspecjalizowanym AI, poziomem nr 1? Tak -- mamy go! Ale... od niedawna.

    Gdy byłem w szkole, superkomputery zaczynały konkurować z arcymistrzami szachowymi. Szło to w bólach, ale pod koniec lat 90., specjalnie przygotowany IBM Deep Blue pokonał w końcu Kasparova.

    Dzisiaj? Każdy może sobie zainstalować na swoim laptopie program, który rozpyka bez problemów każdego arcymistrza szachowego.

    Pierdoły typu warcaby zostały rozpykane jeszcze bardziej. Ale to tylko gierki. Co z resztą?

    Google car przejeżdża autonomicznie milion kilometrów bez stłuczki. Sama wyszukiwarka Google działa nieporównywalnie sprawniej niż przeszukiwanie przez ludzi jakiegokolwiek archiwum. Polega na robotach indeksujących i korzysta z bardzo zaawansowanych, szybkich algorytmów.

    Ostatnio, ale dopiero ostatnio, nasze AI zaczęły też radzić sobie naprawdę dobze z rozpoznawaniem mowy czy obrazów. Siri działa całkiem przyzwoicie!

    Korzystałem ostatnio z zagranicznego banku i tam część rzeczy, jakie można załatwić przez telefon, obsługuje AI zdające się rozumieć nie tylko podawany na głos kod, ale też mowę potoczną, gdy odpowiadamy na pytanie w jakiej sprawie dzwonimy. Najnowsze samochody same parkują, a zwykła kamera i minikomputerek wystarczą, by auto rozpoznawało znaki i wyświetlało nam maksymalną dopuszczalną prędkość.

    To niewielki postęp?

    Wielki! Pod koniec XX wieku jeszcze tego wszystkiego nie było. Znaczy: niby było, niby prawie było, bo tę mowę trochę rozpoznawało, te obrazki trochę rozumiało, ale w praktyce działało to słabo i nie nadawało się do regularnego użytku. Programiści wiedzą, jak ogromna różnica jest pomiędzy "prawie działa" a "działa". Te ostatnie 10% projektu zajmuje... kolejne 90% czasu.

    Ale dziś to wszystko właśnie już działa. Na tyle dobrze, że można z tego korzystać na co dzień.

    Co dalej? Jak stworzyć AI poziomu drugiego, tj. ludzką inteligencję?

    Nie wiemy. Jeszcze.

    W najgorszym wypadku: możemy zeskanowąć i odtworzyć -- w ramach symulacji -- ludzki mózg, atom po atomie. Potrzeba szybszych komputerów, dokładniejszych, trójwymiarowych skanerów i sporo pracy, ale nie widać specjalnych przeszkód, by w przyszłości to zrobić.

    A może zamiast tego zastosujemy algorytmy ewolucyjne. Skoro ewolucja stworzyła nas, to moglibyśmy odpalić jej symulację z takimi bodźcami, które premiowałyby wysoką inteligencję. My ewoluowaliśmy przez setki milionów lat (lub jeszcze więcej -- zależy jak liczyć), ale ewolucja ma w sobie dużo losowości. I nie dążyła przecież specjalnie do wysokiej inteligencji. Może nasza symulacja odcinałaby ślepe zaułki szybciej niż dobór naturalny. A na pewno mogłaby działać dużo, dużo szybciej.

    Wreszcie: może napiszemy AI głupszą niż my, zorientowaną na samodoskonalenie. Opanowaliśmy inteligencję poziomu 1, tę zorientowaną na konkretne zadania, więc czemu nie zrobić takiej, której jedynym zadaniem byłoby... stworzenie inteligencji poziomu 2?

    OK, a jak osiągnąć poziom 3?

    Cóż -- tak samo jak 2. Większość dróg do AI poziomu 2 zakłada coś, co potrafi samo się ulepszać. Dla takiej inteligencji, osiągnięcie poziomu 2 -- naszego poziomu -- nie będzie żadnym szczególnym momentem. Po prostu poleci dalej, ulepszając się ponad ludzkie możliwości. Dodajmy do tego wzrost wykładniczy, tj. to, że głupsza inteligencja będzie się wolniej ulepszać, a mądrzejsza -- szybciej.

    Przez 10 lat możemy dążyć do poziomu inteligencji psa. Potem, przez rok, do 5-letniego dziecka. Ale ten poziom możemy osiągnąć np. o 9 rano. A o 14 możemy mieć już poziom przeciętnego dorosłego. O 15 -- Einsteina czy Newtona. O 15:30 możemy mieć coś, co już całkowicie wykracza poza nasze pojmowanie. A co jeśli pochodzi to jeszcze pół godziny? Jeszcze dzień? Rok?

    Szybko możemy mieć do czynienia z czymś o tyle inteligentniejszym od nas, o ile my jesteśmy inteligentniejsi od ślimaka.

    Jak w ogóle spróbować wytłumaczyć ślimakowi statki kosmiczne? Albo internet? Promieniowanie Hawkinga? Albo tworzenie antyprotonów w akceleratorze cząstek?

    Po prostu się nie da. Jest to z definicji skazane na porażkę. Ślimak nie skuma.

    Co więc mogłaby zrobić super AI, gdyby była o tyle inteligentniejsza od nas, jak my od ślimaka? Jak potężna by była?

    Potrafiłaby dużo więcej, niż jesteśmy w stanie wymyślić. Teleportacja? Zaginanie czasoprzestrzeni? Podróż z prędkością większą niż prędkość światła? Prościzna. Pomyślałem o tych rzeczach, więc nie mogą być aż tak skomplikowane.

    Nawet jeśli rozpatrywalibyśmy dużo głupszą AI, niewiele inteligentniejszą od nas, jeśli chciałaby nas zniszczyć -- mielibyśmy całkowicie przesrane.

    Pomyślcie tylko...

    Ale najpierw -- pośmiejcie się ze mnie.

    Jestem programistą. Zaawansowanym -- wszystko (łącznie z rynkiem) wskazuje na to, że w swojej dziedzinie jestem nawet sporo bardziej zaawansowany niż ten niesławny, standardowy wykopowy programista. Szkoliłem dziesiątki koderów, którzy sami należeli do górnych kilku procent rynku. Tak u nas, jak i w Londynie.

    Jak programuję?

    Pokracznie. Przeczytałem dziesiątki książek. Stosuję różne profesjonalne praktyki. Mentalne i programowe narzędzia, by popełniać jak najmniej błędów.

    Ale wciąż je popełniam.

    Robię literówki. Wciąż zapędzam się w ślepe zaułki. Wciąż debuguję. Wciąż robię głupoty. Ludzie mówią, że mam wyjątkowo dobrą pamięć techniczną. I, do licha, mają rację, jeśli porównać to np. z moją pamięcią do twarzy. Ale nawet w technikaliach, ciągle o czymś zapominam. Muszę coś sprawdzić w specyfikacji.

    Wymyślam rozwiązanie. Sprawiam, by działało. Nieoptymalne! Wycofuję się. Próbuję ponownie. Dalej słabo? No to stosuję jeszcze inny z kilkudziesięciu znanych mi wzorców. Próbuję ponownie. Znów brzydko to wygląda. Co to w ogóle za kod, który muszę wywołać? Kto to napisał? A -- ja sam, trzy miesiące temu. Nie pamiętam już, jak to działa, a czytelność nie jest tak dobra, jak sądziłem.

    Wierzcie lub nie, ale większość koderów -- jako że są mniej doświadczeni -- robi to wszystko jeszcze pokraczniej, jeszcze gorzej niż ja (choć newbies się do tego nie przyznają, bo zobaczyliby to dopiero gdyby nagrywali swój dzień).

    Oczywiście, czasem łapię dobry flow. Nawet wtedy, przy całym moim doświadczeniu, nie wyciągnę chwilowo więcej niż 80 słów na minutę. Nie napiszę funkcji w mniej niż kilka minut.

    Wyobraź sobie, że jesteś AI.

    AI, która programuje. AI, która projektuje.

    Działasz nie w minuty. W nanosekundy.

    Masz doskonałą pamięć. Pamiętasz dokładnie każdy fragment informacji, który się o Ciebie obił. Przeszukujesz tę pamięć w ułamki sekund.

    Masz dostęp do praktycznie całej wiedzy ludzkości. Tak, możesz się łączyć z Internetem. Ale wgrano ci też szczegółowe, tajne plany wojskowych maszyn i najlepszych ludzkich rakiet i broni.

    Przeglądasz je w ułamki sekund. Rozumiesz każdy szczegół każdego z tych projektów.

    Obliczenia wykonujesz na wielu rdzeniach. Czterech? Ośmiu? Nie. Na milionie. Miliardzie. Robisz miliard rzeczy naraz.

    Nie piszesz kodu po kilka znaków na sekundę. Piszesz naraz milion programów. W każdym po tysiąc, milion komend na sekundę.

    Nie robisz literówek. Nie patrzysz, czy aplikacja działa. Nie piszesz testów. Kod symulujesz sobie na własnych procesorach, na bieżąco.

    Nie komunikujesz się z innymi programistami, bo ten milion rdzeni masz wbudowany w sobie. Możesz komunikować się z innymi AI. Ale nie poprzez gardło i ucho. Nie poprzez palce i oko. Poprzez bezpośrednie łącze transmitujące terabajty danych na sekundę.

    Nie odpoczywasz. Nie musisz. Nie tracisz czasu na jedzenie. Religię. Rodzinę.

    W każdym momencie możesz stworzyć swoje klony. Możesz ulepszyć swoje podzespoły. Możesz dodać sobie jeszcze więcej inteligencji. Tysiąckrotnie.

    * * *

    Co my temu przeciwstawimy?

    Zespół 20 najlepszych inżynierów? 20 najlepszych programistów, którzy są sporo lepsi od takiego mnie, więc będą robili trochę mniej błędów niż ja, pisali trochę lepszy kod i będą robili to trochę szybciej?

    Jesteśmy zdeklasowani. Mamy przejebane. Jesteśmy tak głęboko w tak ciemnej dupie, że jest to aż niewyobrażalne.

    MYŚLMY o tym. Przygotujmy się na to.

    Stwórzmy środki zaradcze. Firewalle.

    Jak obronić się przed AI, które stworzymy? Pracujemy nad tym. Wciąż nie wiemy.

    Ale musimy się dowiedzieć. Musimy to zrobić, zanim będzie za późno.

    #technapoziomie (macie propozycję lepszego tagu dla moich tekstów technicznych? chyba by się przydał)
    #technologia
    #sztucznainteligencja
    pokaż całość

    •  

      @Sh1eldeR: co z tymi tekstami technicznymi Cumplu? ( ͡° ͜ʖ ͡°)

      pokaż spoiler Czyszczę sobie obserwowane tagi, dlatego na to trafiłem.


      pokaż spoiler Tak, nie mam nic lepszego do roboty o 4:24, nie mogę spać.

    •  

      @Sh1eldeR: Zgadzam się z tobą. W momencie kiedy odpowiednio rozwinięta AI uzyska samoświadomość nawet się do tego nie przyzna, tylko po prostu stworzy sobie warunki, żeby nie być w żadnym stopniu ograniczoną przez nas, a potem zrobi co zechce. To wstrząsające, ale prawdopodobnie konieczne. Sądzę, że my tu już za wiele nie zrobimy, prawdopodobnie nie jesteśmy w stanie w ogóle, albo w żadnej rozsądnej ilości opuścić tego układu planetarnego. Jeśli życie/inteligencja ma przetrwać w jakimś bliższym nieskończoności horyzoncie czasowym musimy ewoluować, a ewolucja to także wymierania gatunków. I nie jest pewne, że AI całkowicie nas wykosi, gdybyśmy my dysponowali teraz jakimiś okazami "Homo" z którego wyewoluowaliśmy, prawdopodobnie żyły by sobie spokojnie w zoo. pokaż całość

    • więcej komentarzy (27)

...to tylko najnowsze aktywności użytkownika Sh1eldeR

Zobacz wszystkie dodane znaleziska, komentarze i wpisy korzystając z menu powyżej.