Skocz do zawartości

Pierwsze kroki z modułem Nvidia Jetson Orin NX


Pomocna odpowiedź

@Harnas Popsułeś mi plan na następne wpisy 😉 Chciałem zacząć od pokazania, że na Jetson-ie można uruchomić inferencję bez zmian w kodzie, a później wypróbować różne metody optymalizacji i przejście na float16 oraz int8 jak najbardziej jest w planach.

Używam batcha o wielkości 1 bo chciałbym na razie mieć proste oszacowanie mocy obliczeniowej... ale przede wszystkim chcę porównać Jetson Orin z rozwiązaniami Xilinx-a. Mam wyniki dla czasów inferencji na VCK190 (https://www.xilinx.com/products/boards-and-kits/vck190.html), chwilowo tylko z 1 batchem. Pewnie później przejdę i do bardziej zaawansownych testów. Niestety dla FPGA sama synteza DPU zajmuje godzinę. Stąd na razie takie a nie inne testy.

  • Lubię! 1
Link do komentarza
Share on other sites

Dnia 4.07.2023 o 10:42, Elvis napisał:

Używam batcha o wielkości 1 bo chciałbym na razie mieć proste oszacowanie mocy obliczeniowej

To ma pewną wadę, bo mocniejsze układy będą miały mniejszy obserwowany przyrost mocy. Możesz poczytać o tym więcej:

https://blog.paperspace.com/how-to-maximize-gpu-utilization-by-finding-the-right-batch-size/
https://www.pugetsystems.com/labs/hpc/gpu-memory-size-and-deep-learning-performance-batch-size-12gb-vs-32gb-1080ti-vs-titan-v-vs-gv100-1146/

Chyba że liczy się tylko przetwarzanie jednego obrazu naraz, takie przetwarzanie obrazu z kamery w czasie rzeczywistym.

  • Lubię! 1
Link do komentarza
Share on other sites

Od poprzedniego wpisu minęło już trochę czasu, myślałem że kolejne testy zajmą mi chwilę, ale wyszło jak zwykle. Okazało się, że pakiety oprogramowania dostarczone przez Nvidię są nieco trudniejsze w użyciu niż się wydaje, a opanowanie ich wersji jest niemałym wyzwaniem. Zacznę więc od od krótkiego podsumowania tego co dotychczas udało mi się nauczyć.

Płytki serii Jetson są dostarczane z pakietem oprogramowania nazwanym JetPack. W jego skład wchodzi dystrybucja Linux-a oparta o Ubuntu oraz wszystkie niezbędne biblioteki i sterowniki przeznaczone dla procesorów graficznych. Płytki otrzymałem z wersją 5.1 oprogramowania, natomiast aktualnie najnowsza wersja to 5.1.1 (https://developer.nvidia.com/embedded/jetpack-sdk-511).

Aktualizacja jest teoretycznie prostym procesem, można ją wykonać za pomocą managera pakietów (apt). Niestety ta opcja zakończyła się niepowodzeniem i system został w wersji tylko częściowo uaktualnionej... Na szczęście to były dopiero początki zabawy z płytkami, więc najprostszą opcją było skasowanie zawartości pamięci eMMC lub dysku SSD i wgranie systemu od nowa. To okazało się dość prostym procesem, w przypadku płytki  z układem Orin NX opis procedury znajdziemy pod adresem: https://wiki.seeedstudio.com/reComputer_J4012_Flash_Jetpack/. Aktualizacja płytki z układem Orin AGX NX jest jeszcze łatwiejsza, Nvidia dostarcza narzędzie o nazwie SDK Manager (https://docs.nvidia.com/sdk-manager/system-requirements/index.html), za pomocą którego można łatwo przejść całą procedurę aktualizacji.

W skład najnowszej wersji zestawu JetPack 5.1.1 wchodzą następujące moduły:

  • Jetson Linux 35.3.1 (https://developer.nvidia.com/embedded/jetson-linux-r3531) - system operacyjny oparty o Ubuntu LTS 20.04
  • CUDA Toolkit 11.4.19 - zestaw narzędzi umożliwiających pisanie programów działających na procesorze graficznym (GPU)
  • cuDNN 8.6.0 - biblioteka modułów przyspieszających pracę głębokich sieci neuronowych, oparta o bibliotekę CUDA
  • TensorRT 8.5.2 - wysoko zoptymalizowana biblioteka przeznaczona do używania (nie nauki) sieci neuronownych

Bibliotek jest oczywiście więcej, ale wymieniłem te najciekawsze dla AI. Tym co planuję opisać w kolejnej części jest biblioteka TensorRT (https://developer.nvidia.com/tensorrt), która jak mam nadzieję pozwoli na przyspieszenie działania sieci.

Wspomniałem o problemach z wersjami - okazuje się, że wszystko działa poprawnie, ale jeśli np. będziemy chcieli trenować sieć i konwertować wynikowe pliki na innym komputerze (czyli PC), różnice wersji stają się koszmarem i nie wszystko (albo raczej nic) nie chce działać z innymi wersjami na płytce ewaluacyjnej.

Pewnym wyjściem, albo raczej ucieczką od problemu było wykorzystanie płytki Jetson do konwersji plików... to dość powolna opcja, ale działająca.

Na zakończenie jeszcze link do listy wersji JetPack-ów: https://developer.nvidia.com/embedded/jetpack-archive Warto na nie zwrócić uwagę przed zakupem płytki. Okazuje się, że najnowsze wersje są dostępne tylko dla niektórych modeli płytek i dla Jetson Orin otrzymamy Jetpack w wersji 5.1.1, ale już dla Jetson Nano ostatnią wersją jest 4.6.4.

  • Lubię! 1
Link do komentarza
Share on other sites

14 minut temu, Elvis napisał:

jeśli np. będziemy chcieli trenować sieć i konwertować wynikowe pliki na innym komputerze (czyli PC), różnice wersji stają się koszmarem i nie wszystko (albo raczej nic) nie chce działać z innymi wersjami na płytce ewaluacyjnej.

Nie znam szczegółów, ale powszechne jest używanie standardowych formatów np.: https://github.com/huggingface/safetensors (a więcej ich jest porównane w readme). Całą idea https://huggingface.co/models jest to, że wyuczone modele na nie wiadomo jakim sprzęcie mogą być używane przez każdego.

Edytowano przez Harnas
  • Lubię! 1
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Instrukcję szybkiego startu z biblioteką TensorRT znajdziemy pod adresem: https://docs.nvidia.com/deeplearning/tensorrt/quick-start-guide/index.html, natomiast osoby zainteresowane dokładniejszą dokumentacją znajdą więcej informacji pod adresem: https://developer.nvidia.com/tensorrt. Moje eksperymenty bazują na pierwszym tutorialu i nie są kompletnym opisem możliwości TensorRT. Ale mam nadzieję, że chociaż ogólnie pokażą możliwości tej biblioteki.

Na początek warto przyjrzeć się proponowanym w tutorialu scenariuszom użycia biblioteki:

tensorrt_flow.thumb.png.8ab5d513f6bc5b3fe939a3b4fc98a018.png

W poprzednich wpisach wykorzystywałem PyTorch więc żeby móc porównywać wyniki pomiarów zostanę przy tej bibliotece. Jak widać pierwszym krokiem jest konwersja modelu na format ONNX (https://en.wikipedia.org/wiki/Open_Neural_Network_Exchange), a następnie napisanie programu w C++ lub skryptu w Pythonie do przetestowania działania sieci.

Do testów wykorzystam tę samą co poprzednio sieć Resnet50 i zapiszę ją w formacie ONNX następującym skryptem:

from torchvision.models import resnet50, ResNet50_Weights
import torch.onnx

BATCH_SIZE = 1
dummy_input=torch.randn(BATCH_SIZE, 3, 224, 224)

weights = ResNet50_Weights.IMAGENET1K_V1
model = resnet50(weights=weights)

torch.onnx.export(model, dummy_input, "resnet50_onnx_model.onnx", verbose=False)

Wynikowy plik ma wielkość 98MB.

Następnym krokiem jest konwersja z formatu ONNX na format używany przez TensorRT. Można do tego napisać własny skrypt w Pythonie, albo użyć narzędzia o nazwie trtexec. To właśnie tutaj pojawiają się problemy z wersjami biblioteki - na szczęście trtexec można uruchomić na płytce Jetson, konwersja zajmie trochę czasu, ale chociaż wersja narzędzia będzie dokładnie taka sama jak biblioteki.

Najprostsze wywołanie programu ma postać:

/usr/src/tensorrt/bin/trtexec --onnx=resnet50_onnx_model.onnx --saveEngine=resnet50.trt

Nie mam pojęcia dlaczego Nvidia zdecydowała się instalować pliki binarne w katalogu /usr/src, ale takie są narzędzia tworzone przez duże korporacje - robią co chcą i jak chcą 😞 W każdym razie w chwili otrzymamy plik o nazwie resnet50.trt, którego wielkość będzie bardzo zbliżona do pliku onnx (99MB).

Teraz możemy przystąpić do napisania skryptu, który wczyta ten plik i przetestuje działanie sieci.

Na początek kod bardzo podobny do używanego poprzednio - zaimportowane biblioteki, wczytanie obrazu oraz wykonanie pre-processingu. Dodałem funkcję "softmax_stable", której użyję później oraz wykonałem konwersję tensora na tablicę NumPy:

import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import time

from torchvision.models import resnet50, ResNet50_Weights
import torch
import requests
from PIL import Image
import io

def load_image(url):
    res = requests.get(url).content
    return Image.open(io.BytesIO(res))

def softmax_stable(x):
    return(np.exp(x - np.max(x)) / np.exp(x - np.max(x)).sum())

weights = ResNet50_Weights.IMAGENET1K_V1

#img = load_image("https://forbot.pl/blog/wp-content/uploads/2021/07/FORBOT_kurs_STM32L4_IR_NEC_liczniki.jpg")
#img = load_image("https://cdn.forbot.pl/blog/wp-content/uploads/2021/04/FORBOT_kurs_STM32L4_watchdog.jpg")
img = load_image("https://cdn.forbot.pl/blog/wp-content/uploads/2021/05/FORBOT_kurs_STM32L4_przerwania_procedury.jpg")

preprocess = weights.transforms()
input = preprocess(img).unsqueeze(0).numpy()
input = np.ascontiguousarray(input)

Wczytanie pliku z siecią wygląda następujaco:

runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
with open('resnet50.trt', 'rb') as f:
    engine = runtime.deserialize_cuda_engine(f.read())
context = engine.create_execution_context()

Kolejnym krokiem jest utworzenie bufora na dane wyjściowe oraz buforów w pamięci GPU na dane wejściowe i wyjściowe:

output = np.empty(1000, dtype=np.float32)

d_input = cuda.mem_alloc(input.nbytes)
d_output = cuda.mem_alloc(output.nbytes)
stream = cuda.Stream()

Uruchomienie sieci jest dość proste. Zaczynamy od skopiowania danych z CPU do GPU, następnie uruchamiamy sieć i kopiujemy dane wynikowe z GPU do CPU:

t1 = time.time()
cuda.memcpy_htod_async(d_input, input, stream)
context.execute_async_v2([d_input, d_output], stream.handle, None)
context.execute_async_v2([d_input, d_output], stream.handle, None)
cuda.memcpy_dtoh_async(output, d_output, stream)
stream.synchronize()
t2 = time.time()
print("Result: {:.2f} ms\n".format((t2-t1)*1000))

Na koniec możemy wypisać wyniki w postaci czytelnej dla użytkownika:

results = softmax_stable(output)
for id in results.argsort()[-3:][::-1]:
    cat = weights.meta["categories"][id]
    rate = results[id]
    print("{}: {:.2f}%".format(cat, 100*rate))

Uruchomienie programu daje następujace rezultaty:

Result: 25.07 ms

pizza: 99.74%
meat loaf: 0.04%
French loaf: 0.03%

Jak widać sama sieć działa podobnie jak poprzednio, gdy używałem PyTorch, ale obliczenia zajęły 25ms zamiast 146ms. To spora różnica, bo mógłbym przetwarzać 40 klatek na sekundę, zamiast niecałych 7 w poprzedniej wersji.

 

  • Lubię! 1
Link do komentarza
Share on other sites

Podczas trenowania sieci ważna jest dokładność obliczeń i niejako standardem jest używanie 32-bitowego typu zmiennopozycyjnego (float32). Jednak gdy używamy sieci, precyzja ma o wiele mniejsze znaczenie i częstą praktyką jest używanie typów wykorzystujących mniejszą liczbę bitów.

Biblioteka TensorRT pozwala na bardzo łatwą zmianę precyzji na 16-bitowy typ zmiennopozycyjny (fp16) lub nawet 8-bitowy stałopozycyjny (int8).

Zacznijmy od trybu 16-bitowego. Aby z niego skorzystać wystarczy podczas wywołania trtexec użyć opcji --fp16:

/usr/src/tensorrt/bin/trtexec --fp16 --onnx=resnet50_onnx_model.onnx --saveEngine=resnet50_fp16.trt

Teraz plik wynikowy, czyli resnet50_fp16.trt ma wielkość 50MB. Co ciekawe jedyna zmiana w skrypcie testowym to użycie nowej nazwy pliku (resnet50_fp16.trt, zamiast resnet50.trt). Dane wejściowe i wynikowe są nadal zapisane jako float32, tylko obliczenia są wykonywane z mniejszą precyzją.

Po uruchomieniu otrzymujemy następujące wyniki:

Result: 10.49 ms

pizza: 99.76%
meat loaf: 0.04%
French loaf: 0.03%

Jak widać sieć nadal bardzo dobrze rozpoznaje obraz, ale wielkość pliku zmniejszyła się o połowę, a czas obliczeń spadł do 10.5ms. To daje nam 95 klatek na sekundę.

Warto jeszcze wypróbować opcję --int8, która wykona konwersję sieci do postaci liczb 8-bitowych. Domyślne użycie trtexec nie jest idealne o czym napiszę za chwilę, ale bardzo proste:

/usr/src/tensorrt/bin/trtexec --int8 --onnx=resnet50_onnx_model.onnx --saveEngine=resnet50_fp16.trt

Wielkość pliku z siecią spadła do 26, a po uruchomienie programu otrzymujemy:

Result: 6.04 ms

pizza: 99.46%
French loaf: 0.07%
menu: 0.05%

Jak widać wyniki są prawie identyczne, a czas działania to niewiele ponad 6ms, czyli 165 klatek na sekundę! Niestety nie wszystkie zdjęcia będą dawały tak dobre rezultaty. Konwersja na int8 wymaga nieco więcej pracy, ale nawet użycie domyślnych opcji daje ciekawe wyniki.

Wspomnę jeszcze tylko, że testy na FPGA, a dokładniej układzie Versal ACAP, który sam kosztuje więcej niż płytka Jetson Orin AGX daje rezultaty gorsze niż Jetson Orin NX.

  • Lubię! 1
Link do komentarza
Share on other sites

Obrazek z pizzą był rozpoznawany niemal idealnie przez wszystkie wersje sieci. Niestety w przypadku watchdoga, było już trochę słabiej:

FORBOT_kurs_STM32L4_watchdog.jpg

Skrypt używający PyTorch-a rozpoznawał to zdjęcie następująco:

beagle: 15.33%
muzzle: 14.46%
tennis ball: 8.81%

Wersja TensorRT z precyzją fp32 daje rezultat:

beagle: 14.78%
muzzle: 14.53%
tennis ball: 8.91%

Zmiana na fp16 praktycznie nie ma wpływu na wynik:

beagle: 14.73%
muzzle: 14.50%
tennis ball: 9.00%

Natomiast użycie int8 działa szybko ale mniej dokładnie:

muzzle: 12.85%
tennis ball: 8.12%
beagle: 7.64%

Jak wspominałem wcześniej, użycie int8 może wymagać dodatkowych kroków - automatyczne działanie trtexec nie jest idealne. Nie ma to jednak wpływu na wydajność działania, a ta jest imponująca. Tym bardziej, że nadal testuję tańszy moduł, czyli Jetson Orin NX. Testy Jetson AGX NX postaram się wykonać w przyszłym tygodniu. 

 

  • Lubię! 1
Link do komentarza
Share on other sites

Od poprzedniego wpisu minęło już trochę czasu, należy się więc drobna aktualizacja.

W ostatnim wpisie wspominałem o dwóch interesujących mnie tematach, czyli analizie zdjęć oraz zabawie robocikiem. Pierwszy temat interesuje mnie zawodowo, ale drugi wydał mi się ciekawszy dla czytelników forum, więc postanowiłem się na nim skupić.

Jak napisałem wcześniej, robota JetBot można zmontować samemu, przykładowo drukując obudowę na drukarce 3D albo wykorzystać gotowy zestaw. Brak wolnego czasu sprawia, że kupowanie gotowych zestawów staje się czasem najlepszym wyborem, a w moim przypadku zestaw już od dawna był kupiony, tylko ciągle brakowało czasu na jego przetestowanie.

Złożony robocik wygląda następująco:

IMG_0982.thumb.jpg.eabe1958725c4de95795b02e6a73a237.jpg

Jak widać mamy w zestawie dwie anteny WiFi, na zdjęciu nie jest widoczna karta sieciowa, ale warto wspomnieć że jest podłączona przez PCIe, a nie USB, co  moim zdaniem jest dużym plusem zestawu. Kamerka jest szerokokątna, co też ma plusy i minusy (o tym za chwilę). Sam robocik jest bardzo prosty, chociaż dość solidnie zbudowany. Prędkość jazdy jest raczej nie wyścigowa, jednak całkiem wystarczająca, a wręcz na początek za duża. W zestawie jest jeszcze pad do sterowania robotem oraz trochę drobiazgów, np. zasilacz.  Do kompletu musimy dokupić 3 akumulatorki litowe, który nie ma w zestawie. Warto też zaopatrzyć się w ładowarkę do nich.

Mając zmontowanego robocika czas przystąpić do pierwszego uruchomienia. Instrukcję znajdziemy na stronie producenta: https://www.waveshare.com/wiki/JetBot_AI_Kit

Przyznam, że nie potraktowałem poważnie porady dotyczącej użycia karty o pojemności co najmniej 64GB i szybko pożałowałem. 32GB wystarczą, ale obraz systemu zajmuje ponad 22GB, więc niewiele zostaje wolnego. W pierwszej chwili byłem bardzo zaskoczony tak ogromnym zużyciem pamięci - jednak szybko zrozumiałem przyczynę. Domyślnie dostarczany obraz wykorzystuje kontenery oraz zawiera wszystkie narzędzia potrzebne nie tylko do wykorzystywania sieci (inferencji), ale również do jej trenowania. To tłumaczy zapotrzebowanie na pamięć (również RAM).

  • Lubię! 1
Link do komentarza
Share on other sites

6 minut temu, Elvis napisał:

Kamerka jest szerokokątna,

Próbowałem (w takiej zabawce) użyć typowej kamery. Okazało się, że robot nie jest w stanie zobaczyć tego co powinien widzieć (miał popatrzeć na twarz). Rozwiązaniem było użycie rybiego oka i ograniczenie pola widzenia do 120°

  • Lubię! 1
Link do komentarza
Share on other sites

Wracając do wrażeń z pierwszego uruchomienia... na pewno trzeba uzbroić się w cierpliwość - system jest nie tylko duży, ale i powolny. 

Ale jak już wszystko wystartuje to działa całkiem znośnie i z pewnością wygodnie. Całe sterowanie robimy zdalnie za pomocą przeglądarki:

JetBot-1.thumb.png.e88cd92ac407dc2381e71abccfffbcfa.png

Osoby zaznajomione z Jupyter-em (https://jupyter.org) będą się od razu czuły jak w domu. Interfejs jest bardzo wygodny, a programy w Python-ie możemy pisać i uruchamiać bezpośrednio z okna ulubionej przeglądarki. Bardzo wygodne narzędzie, chociaż jak wspominałem nie ma nic za darmo, więc zużycie zasobów jest niemałe.

Wraz z domyślnym obrazem dostajemy kilka przykładów - na początek podstawy sterowania robotem:

JetBot-2.thumb.png.5793253bdbbc147e5e05c2be815bb6e3.png

Wszystko jest maksymalnie uproszczone, mamy klasę Robot, wystarczy że wywołamy metodę left() lub right() i silniczki zaczną się obracać z zadaną prędkością. Brzmi to banalnie, ale daje sporo radości - jednak ze zdalnie sterowanych samochodzików nigdy się nie wyrasta 😉

Kolejne przykłady pozwalają na sterowanie robota przez dołączonego do zestawu pad-a:

pad.thumb.png.40e954969a96d30c82aa49cf83ed3500.png

Kod programu staje się już nieco ciekawszy, a poznawanie go to całkiem fajna rozrywka - prawdziwa nauka przez zabawę. Osoby, które kiedyś uczyły się nieśmiertelnego Apache+PHP+MySQL mogą być zaskoczone jak bardzo zmieniło się tworzenie aplikacji internetowych (ja byłem). 

JetBot-3.thumb.png.b794a318367a04d2ca97cc0596b331a1.png

W każdym razie sterowanie za pomocą kodu oraz kontrolera daje sporo frajdy, a prawdziwa zabawa zaczyna się po uruchomieniu kamery:

JetBot-4.thumb.png.ded5ae3561189103af46c240a6a4ddb6.png

Widok z kamery pokazuje obraz na żywo, możemy jeździć naszym robotem o obserwować jak widzi świat. A jak wspominałem wcześniej, kamera szerokokątna sprawia, że wiele rzeczy wygląda nieco inaczej. Na zdjęciu powyżej widoczna jest pozioma przeszkoda - ścianka labiryntu dla micromouse. W rzeczywistości ścianka jest prosta, ale kamera widzi ją po swojemu.

Dalej jest już dużo trudniej, chociaż i ciekawiej - zaczynają się przykłady ze sztuczną inteligencją. Do tego jednak trzeba się trochę przygotować, albo raczej przygotować miejsce gdzie robot będzie się mógł poruszać. O tym będzie w kolejnym wpisie.

 

 

 

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Kilka lat temu planowałem budowę robota micromouse - jak zwykle z planów nic nie wyszło, ale chociaż zostały mi elementy do budowy labiryntu. Pozostało jeszcze przygotować podłoże, po którym będzie jeździł robot i do którego da się zamontować elementy tworzące labirynt. Najtańszą i najłatwiej dostępną opcją okazała się płyta gipsowa - może nie piękna i mało trwała, ale zawsze lepsza niż nic.

Kolejne etapy budowy wyglądały następująco:

IMG_0952.thumb.jpg.264dfeb75dff361685163f9496f80289.jpgIMG_0954.thumb.jpg.379b0022e31c90a638c944c98a1bbe88.jpg

IMG_0955.thumb.jpg.a48f6f14860be86ee05df7726f4d035d.jpgIMG_0956.thumb.jpg.5216a3238da608e0bd6d7ee0782a3b36.jpg

Oczywiście w takiej wersji labirynt nie nadawałby się do jazdy, ale zamontowanie wszystkich ścianek pozwalało na upewnienie się czy wszystkie odległości są poprawne.

Przy okazji ciekawostka - różnica między zdjęciem oraz widokiem z kamery:

IMG_0965.thumb.jpg.871389936fd9a1052fdc2110aa757f14.jpgJetBot-4.thumb.png.c376e6e38d3989f77146ddbc83d3bfbb.png

Postanowiłem zacząć od prostego przypadku i z labiryntu zostawiłem tylko obrys:

IMG_0968.thumb.jpg.beac9660e40f3c25319f4453b928a6fe.jpgIMG_0969.thumb.jpg.af4904ab0dde9870d04795c8bc6eab3d.jpg

Robocik oraz trasa do jazdy są gotowe, czas uruchomić kolejny przykład, który ma pokazać jak robot potrafi omijać przeszkody.

Edytowano przez Elvis
  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.