Prototyp do produkcji: rozproszone trenowanie w Vertex AI

1. Omówienie

W tym module użyjesz Vertex AI do uruchomienia rozproszonego zadania treningowego w Vertex AI Training przy użyciu TensorFlow.

Ten moduł należy do serii filmów Prototyp – produkcja. Zanim przejdziesz do tego modułu, ukończ poprzednie moduły. Aby dowiedzieć się więcej, możesz obejrzeć serię filmów towarzyszących:

.

Czego się nauczysz

Poznasz takie zagadnienia jak:

  • Uruchamianie rozproszonego treningu na jednym komputerze z wieloma procesorami graficznymi
  • Przeprowadzanie trenowania rozproszonego na wielu maszynach

Łączny koszt wykonania tego ćwiczenia w Google Cloud wynosi około 2 USD.

2. Wprowadzenie do Vertex AI

W tym module wykorzystano najnowszą ofertę usług AI dostępną w Google Cloud. Vertex AI integruje rozwiązania ML w Google Cloud, zapewniając bezproblemowe środowisko programistyczne. Wcześniej modele wytrenowane za pomocą AutoML i modele niestandardowe były dostępne za pomocą oddzielnych usług. Nowa oferta łączy oba te interfejsy API z innymi nowymi usługami. Możesz też przenieść istniejące projekty do Vertex AI.

Vertex AI zawiera wiele różnych usług, które obsługują kompleksowe przepływy pracy związane z systemami uczącymi się. Ten moduł będzie dotyczył podanych niżej usług: szkolenia i Workbench.

Omówienie usługi Vertex

3. Omówienie rozproszonego trenowania

Jeśli masz tylko 1 kartę GPU, TensorFlow będzie używać tego akceleratora do przyspieszania trenowania modelu bez konieczności wykonywania przez Ciebie dodatkowych czynności. Jeśli jednak chcesz uzyskać dodatkową wydajność dzięki użyciu wielu procesorów graficznych, musisz użyć tf.distribute, czyli modułu TensorFlow do wykonywania obliczeń na wielu urządzeniach.

Pierwsza część tego modułu używa narzędzia tf.distribute.MirroredStrategy, które możesz dodać do aplikacji treningowych, wprowadzając kilka zmian w kodzie. Ta strategia tworzy kopię modelu na każdym GPU na komputerze. Kolejne aktualizacje gradientu będą odbywać się synchronicznie. Oznacza to, że każdy procesor graficzny oblicza ruch do przodu i do tyłu przez model na innym wycinku danych wejściowych. Obliczone gradienty z każdego z tych kawałków są następnie agregowane na wszystkich procesorach graficznych i uśredniane w ramach procesu zwanego all-reduce. Parametry modelu są aktualizowane za pomocą tych średnich gradientów.

Sekcja opcjonalna na końcu modułu używa polecenia tf.distribute.MultiWorkerMirroredStrategy, które jest podobne do MirroredStrategy, z tym że obsługuje więcej niż 1 maszynę. Każda z tych maszyn może też mieć kilka procesorów graficznych. Na przykład MirroredStrategy, MultiWorkerMirroredStrategy to synchroniczna strategia równoległości danych, której można używać, wprowadzając tylko kilka zmian w kodzie. Główna różnica między równoległym przetwarzaniem danych w sposób synchroniczny na jednym komputerze a wielu komputerach polega na tym, że gradienty na końcu każdego kroku muszą być teraz synchronizowane na wszystkich procesorach graficznych na komputerze i na wszystkich komputerach w klastrze.

Aby ukończyć ten moduł, nie musisz znać szczegółów, ale jeśli chcesz dowiedzieć się więcej o tym, jak działa rozproszone uczenie w TensorFlow, obejrzyj film poniżej:

4. Konfigurowanie środowiska

Wykonaj czynności opisane w module Trenowanie modeli niestandardowych przy użyciu Vertex AI, aby skonfigurować środowisko.

5. Jedna maszyna, trenowanie z wieloma GPU

Prześlesz rozproszone zadanie treningowe do Vertex AI, umieszczając kod aplikacji treningowej w kontenerze Dockera i przenosząc ten kontener do Google Artifact Registry. Dzięki temu podejściu możesz wytrenować model utworzony za pomocą dowolnej platformy.

Aby rozpocząć, w menu Menu z aplikacjami w notatniku Workbench utworzonym w poprzednich modułach otwórz okno terminala.

Otwieranie terminala w notatniku

Krok 1. Napisz kod trenowania

Utwórz nowy katalog o nazwie flowers-multi-gpu i przejdź do niego:

mkdir flowers-multi-gpu
cd flowers-multi-gpu

Uruchom następujące polecenie, aby utworzyć katalog na kod trenowania i plik Pythona, w którym umieścisz poniższy kod.

mkdir trainer
touch trainer/task.py

W katalogu flowers-multi-gpu/ powinien teraz znajdować się ten plik:

+ trainer/
    + task.py

Następnie otwórz utworzony plik task.py i skopiuj kod poniżej.

W polu BUCKET_ROOT zastąp wartość {your-gcs-bucket} zasobnikiem Cloud Storage, w którym przechowywany jest zbiór danych o kwiatach utworzony w sekcji Laboratorium 1.

import tensorflow as tf
import numpy as np
import os

## Replace {your-gcs-bucket} !!
BUCKET_ROOT='/gcs/{your-gcs-bucket}'

# Define variables
NUM_CLASSES = 5
EPOCHS=10
BATCH_SIZE = 32

IMG_HEIGHT = 180
IMG_WIDTH = 180

DATA_DIR = f'{BUCKET_ROOT}/flower_photos'

def create_datasets(data_dir, batch_size):
  '''Creates train and validation datasets.'''

  train_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  validation_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  train_dataset = train_dataset.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
  validation_dataset = validation_dataset.cache().prefetch(buffer_size=tf.data.AUTOTUNE)

  return train_dataset, validation_dataset


def create_model():
  '''Creates model.'''

  model = tf.keras.Sequential([
    tf.keras.layers.Resizing(IMG_HEIGHT, IMG_WIDTH),
    tf.keras.layers.Rescaling(1./255, input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
  ])
  return model

def main():  

  # Create distribution strategy
  strategy = tf.distribute.MirroredStrategy()

  # Get data
  GLOBAL_BATCH_SIZE = BATCH_SIZE * strategy.num_replicas_in_sync
  train_dataset, validation_dataset = create_datasets(DATA_DIR, BATCH_SIZE)

  # Wrap model creation and compilation within scope of strategy
  with strategy.scope():
    model = create_model()
    model.compile(optimizer=tf.keras.optimizers.Adam(),
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(),
                  metrics=['accuracy'])

  history = model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=EPOCHS
  )

  model.save(f'{BUCKET_ROOT}/model_output')


if __name__ == "__main__":
    main()

Zanim utworzysz kontener, przyjrzyjmy się kodowi. Korzystanie z trenowania rozproszonego wiąże się z kilkoma komponentami.

  • W funkcji main() tworzony jest obiekt MirroredStrategy. Następnie tworzysz zmienne modelu w ramach strategii. Ten krok informuje TensorFlow, które zmienne powinny być lustrzanymi odbiciami na kartach GPU.
  • Wielkość wsadu jest zwiększana o num_replicas_in_sync. Skalowanie rozmiaru wsadu jest sprawdzoną metodą w przypadku korzystania ze strategii synchronicznego równoległości danych w TensorFlow. Więcej informacji znajdziesz tutaj

Krok 2. Utwórz plik Dockerfile

Aby skonteneryzować kod, musisz utworzyć plik Dockerfile. W pliku Dockerfile musisz umieścić wszystkie polecenia potrzebne do uruchomienia obrazu. Zainstaluje wszystkie niezbędne biblioteki i skonfiguruje punkt wejścia dla kodu trenowania.

W terminalu utwórz pusty plik Dockerfile w katalogu głównym katalogu flowers:

touch Dockerfile

W katalogu flowers-multi-gpu/ powinien teraz znajdować się ten plik:

+ Dockerfile
+ trainer/
    + task.py

Otwórz plik Dockerfile i wklej do niego ten kod:

FROM gcr.io/deeplearning-platform-release/tf2-gpu.2-8

WORKDIR /

# Copies the trainer code to the docker image.
COPY trainer /trainer

# Sets up the entry point to invoke the trainer.
ENTRYPOINT ["python", "-m", "trainer.task"]

Krok 3. Utwórz kontener

W terminalu uruchom to polecenie, aby zdefiniować zmienną środowiskową dla projektu. Pamiętaj, aby zastąpić your-cloud-project identyfikatorem projektu:

PROJECT_ID='your-cloud-project'

Utwórz repozytorium w Artifact Registry. Użyjemy repozytorium utworzonego w pierwszym module.

REPO_NAME='flower-app'

Zdefiniuj zmienną za pomocą identyfikatora URI obrazu kontenera w Artifact Registry:

IMAGE_URI=us-central1-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/flower_image_distributed:single_machine

Skonfiguruj Dockera

gcloud auth configure-docker \
    us-central1-docker.pkg.dev

Następnie utwórz kontener, uruchamiając to polecenie w katalogu głównym flowers-multi-gpu:

docker build ./ -t $IMAGE_URI

Na koniec prześlij go do Artifact Registry:

docker push $IMAGE_URI

Po przeniesieniu kontenera do Artifact Registry możesz rozpocząć zadanie trenowania.

Krok 4. Uruchom zadanie za pomocą pakietu SDK

W tej sekcji dowiesz się, jak skonfigurować i uruchomić zadanie scentralizowanego trenowania za pomocą pakietu Vertex AI Python SDK.

W menu utwórz notatnik TensorFlow 2.

new_notebook

Zaimportuj pakiet Vertex AI SDK.

from google.cloud import aiplatform

Następnie określ CustomContainerTrainingJob.

Musisz zastąpić reguły {PROJECT_ID} w: container_uri i {YOUR_BUCKET} w: staging_bucket.

job = aiplatform.CustomContainerTrainingJob(display_name='flowers-multi-gpu',
                                            container_uri='us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_distributed:single_machine',
                                            staging_bucket='gs://{YOUR_BUCKET}')

Po zdefiniowaniu zadania możesz je uruchomić. Ustaw liczbę akceleratorów na 2. Jeśli użylibyśmy tylko 1 układu GPU, nie byłoby to rozproszone trenowanie. W przypadku rozproszonego trenowania na jednej maszynie używasz co najmniej 2 akceleratorów.

my_custom_job.run(replica_count=1,
                  machine_type='n1-standard-4',
                  accelerator_type='NVIDIA_TESLA_V100',
                  accelerator_count=2)

W konsoli możesz sprawdzać postępy zadania.

multigpu_job

6. [Opcjonalnie] Szkolenie wielu pracowników

Po wypróbowaniu rozproszonego trenowania na jednej maszynie z wieloma procesorami graficznymi możesz teraz jeszcze bardziej rozwinąć swoje umiejętności trenowania rozproszonego, trenując na wielu komputerach. Aby obniżyć koszty, nie będziemy dodawać do tych maszyn żadnych procesorów graficznych, ale jeśli chcesz, możesz eksperymentalnie dodać GPU.

Otwórz nowe okno terminala w notatniku:

Otwieranie terminala w notatniku

Krok 1. Napisz kod treningowy

Utwórz nowy katalog o nazwie flowers-multi-machine i znajdź do niego dysk CD:

mkdir flowers-multi-machine
cd flowers-multi-machine

Uruchom te polecenia, aby utworzyć katalog dla kodu szkoleniowego i plik Pythona, w którym umieścisz kod podany poniżej.

mkdir trainer
touch trainer/task.py

W katalogu flowers-multi-machine/ powinien teraz znajdować się ten plik:

+ trainer/
    + task.py

Następnie otwórz utworzony plik task.py i skopiuj kod poniżej.

W polu BUCKET_ROOT zastąp wartość {your-gcs-bucket} zasobnikiem Cloud Storage, w którym przechowywany jest zbiór danych o kwiatach utworzony w sekcji Laboratorium 1.

import tensorflow as tf
import numpy as np
import os

## Replace {your-gcs-bucket} !!
BUCKET_ROOT='/gcs/{your-gcs-bucket}'

# Define variables
NUM_CLASSES = 5
EPOCHS=10
BATCH_SIZE = 32

IMG_HEIGHT = 180
IMG_WIDTH = 180

DATA_DIR = f'{BUCKET_ROOT}/flower_photos'
SAVE_MODEL_DIR = f'{BUCKET_ROOT}/multi-machine-output'

def create_datasets(data_dir, batch_size):
  '''Creates train and validation datasets.'''

  train_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  validation_dataset = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(IMG_HEIGHT, IMG_WIDTH),
    batch_size=batch_size)

  train_dataset = train_dataset.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
  validation_dataset = validation_dataset.cache().prefetch(buffer_size=tf.data.AUTOTUNE)

  return train_dataset, validation_dataset


def create_model():
  '''Creates model.'''

  model = tf.keras.Sequential([
    tf.keras.layers.Resizing(IMG_HEIGHT, IMG_WIDTH),
    tf.keras.layers.Rescaling(1./255, input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
  ])
  return model

def _is_chief(task_type, task_id):
  '''Helper function. Determines if machine is chief.'''

  return task_type == 'chief'


def _get_temp_dir(dirpath, task_id):
  '''Helper function. Gets temporary directory for saving model.'''

  base_dirpath = 'workertemp_' + str(task_id)
  temp_dir = os.path.join(dirpath, base_dirpath)
  tf.io.gfile.makedirs(temp_dir)
  return temp_dir


def write_filepath(filepath, task_type, task_id):
  '''Helper function. Gets filepath to save model.'''

  dirpath = os.path.dirname(filepath)
  base = os.path.basename(filepath)
  if not _is_chief(task_type, task_id):
    dirpath = _get_temp_dir(dirpath, task_id)
  return os.path.join(dirpath, base)

def main():
  # Create distribution strategy
  strategy = tf.distribute.MultiWorkerMirroredStrategy()

  # Get data
  GLOBAL_BATCH_SIZE = BATCH_SIZE * strategy.num_replicas_in_sync
  train_dataset, validation_dataset = create_datasets(DATA_DIR, BATCH_SIZE)

  # Wrap variable creation within strategy scope
  with strategy.scope():
    model = create_model()
    model.compile(optimizer=tf.keras.optimizers.Adam(),
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(),
                  metrics=['accuracy'])

  history = model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=EPOCHS
  )

  # Determine type and task of the machine from
  # the strategy cluster resolver
  task_type, task_id = (strategy.cluster_resolver.task_type,
                        strategy.cluster_resolver.task_id)

  # Based on the type and task, write to the desired model path
  write_model_path = write_filepath(SAVE_MODEL_DIR, task_type, task_id)
  model.save(write_model_path)

if __name__ == "__main__":
    main()

Zanim utworzysz kontener, przyjrzyjmy się kodowi. W kodzie jest kilka elementów, które są niezbędne, aby aplikacja do treningu działała z MultiWorkerMirroredStrategy.

  • W funkcji main() tworzony jest obiekt MultiWorkerMirroredStrategy. Następnie tworzysz zmienne modelu w ramach strategii. Ten kluczowy krok informuje TensorFlow, które zmienne powinny być odzwierciedlane na replikach.
  • Wielkość wsadu jest zwiększana o num_replicas_in_sync. Skalowanie rozmiaru partii jest sprawdzoną metodą w przypadku korzystania z synchronicznych strategii równoległości danych w TensorFlow.
  • W przypadku wielu instancji roboczych zapisywanie modelu jest nieco bardziej skomplikowane, ponieważ miejsce docelowe musi być inne dla każdej instancji. Główny pracownik zapisze model w wybranym katalogu, a pozostałe osoby zapiszą go w tymczasowych katalogach. Takie katalogi tymczasowe muszą być unikalne, ponieważ dzięki temu wielu instancji roboczych nie będzie mogło zapisywać danych w tej samej lokalizacji. Zapisy mogą zawierać operacje zbiorcze, co oznacza, że wszyscy pracownicy muszą je zapisać, a nie tylko szef. Funkcje _is_chief(), _get_temp_dir(), write_filepath()main() zawierają kod szablonowy, który pomaga zapisać model.

Krok 2. Utwórz plik Dockerfile

Aby skonteneryzować kod, musisz utworzyć plik Dockerfile. W pliku Dockerfile musisz umieścić wszystkie polecenia potrzebne do uruchomienia obrazu. Zainstaluje wszystkie niezbędne biblioteki i skonfiguruje punkt wejścia dla kodu trenowania.

W terminalu utwórz pusty plik Dockerfile w katalogu głównym katalogu flowers:

touch Dockerfile

W katalogu flowers-multi-machine/ powinien teraz znajdować się ten plik:

+ Dockerfile
+ trainer/
    + task.py

Otwórz plik Dockerfile i wklej do niego ten kod:

FROM gcr.io/deeplearning-platform-release/tf2-gpu.2-8

WORKDIR /

# Copies the trainer code to the docker image.
COPY trainer /trainer

# Sets up the entry point to invoke the trainer.
ENTRYPOINT ["python", "-m", "trainer.task"]

Krok 3. Utwórz kontener

W terminalu uruchom to polecenie, aby zdefiniować zmienną środowiskową dla projektu. Pamiętaj, aby zastąpić your-cloud-project identyfikatorem projektu:

PROJECT_ID='your-cloud-project'

Utwórz repozytorium w Artifact Registry. Użyjemy repozytorium utworzonego w pierwszym laboratorium.

REPO_NAME='flower-app'

Zdefiniuj zmienną z identyfikatorem URI obrazu kontenera w Google Artifact Registry:

IMAGE_URI=us-central1-docker.pkg.dev/$PROJECT_ID/$REPO_NAME/flower_image_distributed:multi_machine

Konfigurowanie Dockera

gcloud auth configure-docker \
    us-central1-docker.pkg.dev

Następnie utwórz kontener, uruchamiając to polecenie w katalogu głównym flowers-multi-machine:

docker build ./ -t $IMAGE_URI

Na koniec przekaż go do Artifact Registry:

docker push $IMAGE_URI

Po przeniesieniu kontenera do Artifact Registry możesz rozpocząć zadanie trenowania.

Krok 4. Uruchom zadanie za pomocą pakietu SDK

W tej sekcji dowiesz się, jak skonfigurować i uruchomić zadanie trenowania rozproszonego za pomocą pakietu Vertex AI Python SDK.

W menu utwórz notatnik TensorFlow 2.

new_notebook

Zaimportuj pakiet Vertex AI SDK.

from google.cloud import aiplatform

Następnie określ worker_pool_specs.

Vertex AI udostępnia 4 pule instancji roboczych, które obsługują różne typy zadań maszynowych.

Pula instancji roboczych 0 konfiguruje instancję główną, główną, planującą lub „główną”. W MultiWorkerMirroredStrategy wszystkie maszyny są oznaczone jako instancje robocze, czyli fizyczne maszyny, na których są wykonywane zreplikowane obliczenia. Oprócz tego, że każda maszyna jest pracownikiem, musi być jeden pracownik, który wykonuje dodatkowe zadania, takie jak zapisywanie punktów kontrolnych i zapisywanie plików podsumowania w TensorBoard. Ten komputer nosi nazwę głównego. Zawsze jest tylko 1 główna instancja robocza, więc liczba instancji roboczych w puli Worker 0 będzie zawsze 1.

Pula instancji roboczych 1 to miejsce, w którym konfigurujesz dodatkowe instancje robocze klastra.

Pierwszy słownik na liście worker_pool_specs reprezentuje pulę instancji roboczych 0, a drugi – pulę instancji roboczych 1. W tym przykładzie obie konfiguracje są identyczne. Jeśli jednak chcesz przeprowadzić szkolenie na 3 maszynach, dodasz dodatkowych pracowników do puli Worker pool 1, ustawiając wartość replica_count na 2. Jeśli chcesz dodać procesory GPU, musisz dodać argumenty accelerator_type i accelerator_count do argumentu machine_spec dla obu pul instancji roboczych. Jeśli z MultiWorkerMirroredStrategy chcesz używać układów GPU, każda maszyna w klastrze musi mieć taką samą liczbę GPU. W przeciwnym razie zadanie się nie powiedzie.

Musisz zastąpić {PROJECT_ID}image_uri.

# The spec of the worker pools including machine type and Docker image
# Be sure to replace PROJECT_ID in the "image_uri" with your project.

worker_pool_specs=[
     {
        "replica_count": 1,
        "machine_spec": {
          "machine_type": "n1-standard-4",
        },
        "container_spec": {"image_uri": "us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_distributed:multi_machine"}
      },
      {
        "replica_count": 1,
        "machine_spec": {
          "machine_type": "n1-standard-4",
        },
        "container_spec": {"image_uri": "us-central1-docker.pkg.dev/{PROJECT_ID}/flower-app/flower_image_distributed:multi_machine"}
      }
          ]

Następnie utwórz i uruchom CustomJob, zastępując {YOUR_BUCKET} w staging_bucket zasobnikiem na potrzeby testowania.

my_custom_job = aiplatform.CustomJob(display_name='flowers-multi-worker',
                                     worker_pool_specs=worker_pool_specs,
                                     staging_bucket='gs://{YOUR_BUCKET}')

my_custom_job.run()

W konsoli możesz sprawdzać postępy zadania.

multi_worker_job

🎉 Gratulacje! 🎉

Wiesz już, jak używać Vertex AI do:

  • Uruchamianie zadań treningowych rozproszonych za pomocą TensorFlow

Więcej informacji o różnych częściach Vertex znajdziesz w dokumentacji.

7. Czyszczenie

Skonfigurowaliśmy notatnik tak, aby przekraczał limit czasu po 60 minutach bezczynności, więc nie musimy się martwić wyłączeniem instancji. Jeśli chcesz ręcznie wyłączyć instancję, w konsoli Vertex AI Workbench kliknij przycisk Zatrzymaj. Jeśli chcesz całkowicie usunąć notatnik, kliknij przycisk Usuń.

Zatrzymaj instancję

Aby usunąć zasobnik Cloud Storage, w menu nawigacyjnym konsoli Cloud przejdź do usługi Storage, wybierz zasobnik i kliknij Usuń:

Usuwanie miejsca na dane