Jak projektować układy GPU
Chapter 5 Gpu Memory System Design

Radna pamięć dynamiczna (DRAM) jest podstawową technologią używaną do implementacji pamięci głównej we współczesnych systemach komputerowych, w tym w GPU. DRAM oferuje wysoką gęstość i stosunkowo niskie koszty w porównaniu z innymi technologiami pamięci. Jednak DRAM ma również wyższą latencję dostępu i niższą przepustowość w porównaniu do pamięci na-chipowej, takich jak pamięci podręczne i rejestry.

Karty graficzne zazwyczaj wykorzystują wyspecjalizowane technologie DRAM, które są zoptymalizowane pod kątem wysokiej przepustowości, a nie niskiej latencji. Niektóre powszechnie stosowane technologie DRAM w GPU to:

  1. GDDR (Graphics Double Data Rate): GDDR to wyspecjalizowana technologia DRAM zaprojektowana dla kart graficznych i konsol do gier. Oferuje ona wyższą przepustowość niż standardowe DRAM DDR, wykorzystując szerszą magistralę i wyższe częstotliwości zegara. GDDR5 i GDDR6 są najnowszymi wersjami, oferującymi przepustowość do 512 GB/s i 768 GB/s.

  2. HBM (High Bandwidth Memory): HBM to wysokowydajna technologia pamięci DRAM 3D-stacked, która zapewnia bardzo wysoką przepustowość i niskie zużycie energii. HBM łączy wiele warstw pamięci DRAM umieszczonych jedna na drugiej i łączy je za pomocą przez-krzem-wylotów (TSV), umożliwiając znacznie wyższe szybkości transferu danych niż tradycyjne DRAM. HBM2 może zapewniać przepustowość do 1 TB/s.

Rysunek 5.1 ilustruje różnicę między tradycyjną pamięcią GDDR a 3D-stacked HBM.


HBM Pamięć
  ____________                   ______________________  
 |            |                 |  ___________________  |
 |   DRAM     |                 | |                   | |
 |   Chipsy    |                 | |      Układy DRAM  | |
 |            |                 | |___________________| |
 |            |                 |           .          |
 |            |                 |           .          | 
 |            |                 |           .          |
 |____________|                 |  ___________________  |
      |                         | |                   | |
     PCB                        | |  Układ logiczny   | |
                                | |  (GPU)            | |
                                | |___________________| |
                                |______________________|

Rysunek 5.1: Porównanie architektur pamięci GDDR i HBM.

Wybór technologii DRAM zależy od konkretnych wymagań GPU, takich jak budżet energetyczny, forma czynnika i przeznaczenie. Wysokowydajne GPU do gier i grafiki profesjonalnej często używają GDDR6 ze względu na jego wysoką przepustowość, podczas gdy HBM2 jest bardziej powszechne w GPU przeznaczonych do centrów danych i obliczeń wysokiej wydajności, gdzie kluczową kwestią jest efektywność energetyczna.

## Kontrolery pamięci i arbitraż

Kontrolery pamięci odpowiadają za zarządzanie przepływem danych między GPU a zewnętrzną pamięcią DRAM. Obsługują one żądania pamięci z rdzeni GPU, planują polecenia DRAM i optymalizują wzorce dostępu do pamięci, aby maksymalizować wykorzystanie przepustowości i minimalizować opóźnienia.

Kontrolery pamięci GPU zazwyczaj wykorzystują wielokanałową konstrukcję, aby zapewnić wysoką przepustowość i równoległy dostęp do pamięci DRAM. Każdy kanał pamięci jest podłączony do jednego lub większej liczby układów DRAM i ma własne magistrale poleceń i danych. Kontroler pamięci rozkłada żądania pamięci na dostępne kanały, aby maksymalizować równoległość i unikać konfliktów kanałów.

Rysunek 5.2 przedstawia uproszczony schemat kontrolera pamięci GPU z czterema kanałami.

Rdzenie GPU | | | | | Kontroler | | pamięci | | | |_____________| | Kanały pamięci

       |  Kontroler |
       |_____________|
         |    |    |    |
        Ch0  Ch1  Ch2  Ch3
         |    |    |    |
        DRAM DRAM DRAM DRAM

Rysunek 5.2: Kontroler pamięci GPU z czterema kanałami.

Arbitraż pamięci jest procesem decydowania, które żądania pamięci powinny być obsługiwane jako pierwsze, gdy istnieje wiele oczekujących żądań. Karty graficzne stosują różne strategie arbitrażu, aby zoptymalizować wydajność systemu pamięci i sprawiedliwość:

  1. Pierwsze przyszło, pierwsze obsługiwane (FCFS): Najprostsza strategia arbitrażu, w której żądania są obsługiwane w kolejności, w jakiej zostały przesłane. FCFS jest sprawiedliwe, ale może prowadzić do suboptymalne wydajności z powodu braku ponownego sortowania żądań.

  2. Kolejka okrężna (RR): Żądania są obsługiwane w cyklicznej kolejności, zapewniając równy priorytet dla wszystkich wnioskodawców. RR zapewnia sprawiedliwość, ale może nie optymalizować pod kątem lokalności lub pilności żądań.

  3. Oparte na priorytetach: Żądania są przypisywane do priorytetów na podstawie różnych kryteriów, takich jak typ żądania (np. odczyt vs. zapis), źródło (np. tekstura vs. pamięć podręczna L2) lub wiek żądania. Żądania o wyższym priorytecie są obsługiwane jako pierwsze.

  4. Świadome terminów: Żądania są planowane w oparciu o ich terminy, aby zapewnić terminowe zakończenie. Jest to szczególnie ważne dla aplikacji graficznych czasu rzeczywistego.

  5. Świadome lokalności: Kontroler pamięci próbuje zaplanować żądania, które uzyskują dostęp do sąsiadujących lokalizacji pamięci, aby zmaksymalizować trafienia w buforze wiersza i zminimalizować nadmiar przełączania i aktywacji pamięci DRAM.

Zaawansowane kontrolery pamięci GPU często stosują kombinację tych strategii arbitrażu, aby osiągnąć najlepszą równowagę między wydajnością, sprawiedliwością i wymaganiami czasu rzeczywistego.

Pamięć współdzielona i pamięci podręczne

Karty graficzne wykorzystują hierarchiczny system pamięci, który obejmuje zarówno zarządzaną przez oprogramowanie, jak i zarządzaną przez sprzęt pamięć podręczną, aby zmniejszyć opóźnienia i zapotrzebowanie na przepustowość głównej pamięci.

Pamięć współdzielona

Pamięć współdzielona jest zarządzaną przez oprogramowanie, wewnętrzną przestrzenią pamięci, która jest współdzielona między wątkami bloku wątków (NVIDIA) lub gruTutaj znajduje się tłumaczenie na język polski pliku kgroup (OpenCL). Komentarze w kodzie zostały przetłumaczone, a sam kod pozostał niezmieniony.

kgroup (OpenCL) to mechanizm kontrolowany przez użytkownika, który działa jak pamięć podręczna, umożliwiając programistom jawne zarządzanie ruchem danych i ich ponowne wykorzystanie w obrębie bloku wątków.

Pamięć współdzielona jest zwykle implementowana przy użyciu szybkich, wieloportowych banków SRAM, aby zapewnić dostęp o niskiej latencji i wysokiej przepustowości. Każdy bank może obsłużyć jedno żądanie pamięci na cykl, dlatego sprzęt musi arbitrować między współbieżnymi dostępami do tego samego banku, aby uniknąć konfliktów.

Rysunek 5.3 ilustruje organizację pamięci współdzielonej w rdzeniu GPU.

        Blok wątków
   ______________________
  |  _________________   |
  | |    Wątek 0     |  |
  | |_________________|  |
  |         .            |
  |         .            |
  |         .            |
  |  _________________   |
  | |    Wątek N-1   |  |
  | |_________________|  |
  |______________________|
             |
     ________|________
    |                 |
    |  Pamięć Wspólna  |
    |  ____________   |
    | | Bank 0     |  |
    | |____________|  |
    | | Bank 1     |  |
    | |____________|  |
    |       .         |
    |       .         |
    |       .         |
    | | Bank M-1   |  |
    | |____________|  |
    |_________________|

Rysunek 5.3: Organizacja pamięci współdzielonej w rdzeniu GPU.

Właściwe korzystanie z pamięci współdzielonej może znacząco poprawić wydajność jąder GPU przez zmniejszenie liczby dostępów do wolniejszej, zewnętrznej pamięci DRAM. Wymaga to jednak starannego programowania, aby zapewnić efektywne udostępnianie danych i uniknąć konfliktów w bankach.

Pamięci podręczne zarządzane sprzętowo

Oprócz programowo zarządzanej pamięci współdzielonej, GPU wykorzystują również pamięci podręczne zarządzane sprzętowo, aby automatycznie wykorzystywać lokalność danych i zmniejszać dostępy do pamięci DRAM. Najczęstsze typy pamięci podręcznych zarządzanych sprzętowo w GPU to:

  1. Pamięć podręczna L1: Mała, przypisana do każdego rdzenia pamięć podręczna, która przechowuje ostatnio uzyskane dane z pamięci globalnej. Pamięć podręczna L1 jest zazwyczaj prywatna dla każdego rdzenia GPU i służy do zmniejszenia latencji dostępów do pamięci globalnej.

  2. Pamięć podręczna tekstur: Specjalizowana pamięć podręczna zaprojektowana do optymalizacji dostępu do danych przeznaczonych tylko do odczytu.Tutaj jest polska wersja tłumaczenia pliku markdown. Dla kodu, nie tłumaczono kodu, tylko komentarze.

Dane tekstury GPU. Pamięć podręczna tekstur jest zoptymalizowana pod kątem 2D lokalności przestrzennej i obsługuje akcelerowane sprzętowo operacje filtrowania i interpolacji.

  1. Pamięć podręczna stałych: Mała, tylko do odczytu pamięć podręczna, która przechowuje często dostępne dane stałe. Pamięć podręczna stałych jest rozsyłana do wszystkich wątków w warpie, co czyni ją wydajną dla danych, które są współdzielone przez wiele wątków.

  2. Pamięć podręczna L2: Większa, współdzielona pamięć podręczna, która znajduje się między rdzeniami GPU a pamięcią główną. Pamięć podręczna L2 przechowuje dane, które zostały usunięte z pamięci podręcznej L1 i jest używana do zmniejszenia liczby dostępów do pamięci DRAM.

Rysunek 5.4 pokazuje typową hierarchię pamięci GPU z zarządzanymi sprzętowo pamięciami podręcznymi.

      Rdzeń GPU 0         Rdzeń GPU 1         Rdzeń GPU N-1
   ________________     ________________     ________________
  |                |   |                |   |                |
  |    Pamięć      |   |    Pamięć      |   |    Pamięć      |
  |   podręczna    |   |   podręczna    |   |   podręczna    |
  |   danych L1    |   |   danych L1    |   |   danych L1    |
  |________________|   |________________|   |________________|
  |                |   |                |   |                |
  |    Pamięć      |   |    Pamięć      |   |    Pamięć      |
  |  podręczna     |   |  podręczna     |   |  podręczna     |
  |   tekstury     |   |   tekstury     |   |   tekstury     |
  |________________|   |________________|   |________________|
  |                |   |                |   |                |
  |    Pamięć      |   |    Pamięć      |   |    Pamięć      |
  |  podręczna     |   |  podręczna     |   |  podręczna     |
  |   stałych      |   |   stałych      |   |   stałych      |
  |________________|   |________________|   |________________|
         |                     |                     |
         |_____________________|_____________________|
                               |
                        _______|_______
                       |               |
                       |  Pamięć       |
                       |  podręczna L2 |
                       |_______________|
                               |
                               |
                           Pamięć główna

Rysunek 5.4: Hierarchia pamięci GPU z sprzętowo zarządzanymi pamięciami podręcznymiHere is the Polish translation of the provided markdown file, with the code comments translated:

Pamięci podręczne zarządzane sprzętowo pomagają poprawić wydajność aplikacji GPU, automatycznie wykorzystując lokalność danych i zmniejszając liczbę dostępów do pamięci DRAM. Mogą one jednak również wprowadzać wyzwania związane ze spójnością i spójnością pamięci podręcznej, zwłaszcza w kontekście równoległych modeli programowania, takich jak CUDA i OpenCL.

Techniki efektywnego wykorzystania pamięci

Efektywne wykorzystanie systemu pamięci GPU ma kluczowe znaczenie dla osiągnięcia wysokiej wydajności i efektywności energetycznej. Niektóre kluczowe techniki optymalizacji użycia pamięci w aplikacjach GPU obejmują:

  1. Scalanie: Ustawianie dostępów do pamięci z wątków w warptce w sąsiadujących lokalizacjach pamięci, pozwalając sprzętowi na połączenie ich w pojedynczą, szerszą transakcję pamięci. Scalanie maksymalizuje wykorzystanie przepustowości DRAM i zmniejsza liczbę transakcji pamięci.

  2. Optymalizacja układu danych: Organizowanie struktur danych w pamięci, aby maksymalizować lokalność przestrzenną i minimalizować błędy pamięci podręcznej. Obejmuje to techniki takie jak układ struktura-tablic (SoA), który grupuje elementy danych tego samego typu razem, oraz układ tablica-struktur (AoS), który przechowuje elementy danych należące do tej samej struktury razem.

  3. Buforowanie i przetasowywanie: Efektywne wykorzystanie sprzętowo zarządzanych pamięci podręcznych przez wykorzystywanie lokalności czasowej i przestrzennej w modelach dostępu do pamięci. Można to osiągnąć poprzez techniki takie jak kafelkowanie danych, które dzieli dane na mniejsze fragmenty dopasowane do pamięci podręcznej, oraz przetasowywanie programowe, które jawnie ładuje dane do pamięci podręcznej przed ich potrzebą.

  4. Planowanie dostępu do pamięci: Przeplanowanie dostępów do pamięci w celu maksymalizacji trafień w buforze wiersza i minimalizacji opóźnień związanych z odświeżaniem i aktywacją DRAM. Może to być realizowane za pośrednictwem mechanizmów sprzętowych w kontrolerze pamięci lub poprzez techniki programowe, takie jak optymalizacja wzorców dostępu i transformacje układu danych.

  5. Kompresja: Zastosowanie technik kompresji danych w celu zmniejszenia rozmiaru danych przesyłanych między pamięcią a rdzeniami GPU. Może to pomóc wProszę o dostarczenie polskiego tłumaczenia tego pliku Markdown. W przypadku kodu nie tłumaczyć kodu, tylko komentarze.

  6. Wirtualizacja pamięci: Użycie technik wirtualnej pamięci, aby zapewnić ujednoliconą, ciągłą przestrzeń adresową dla aplikacji GPU. Pozwala to na bardziej elastyczne zarządzanie pamięcią i umożliwia takie funkcje, jak strona żądań, co może pomóc w zmniejszeniu zużycia pamięci i poprawie wykorzystania systemu.

Rysunek 5.5 ilustruje niektóre z tych technik w kontekście systemu pamięci GPU.

       Rdzenie GPU
          |
    ______|______
   |             |
   |  Koalescencja|
   |_____________|
          |
    ______|______
   |             |
   | Optymalizacja|
   |   układu    |
   |   danych    |
   |_____________|
          |
    ______|______
   |             |
   |  Buforowanie|
   |  i Prefetch |
   |_____________|
          |
    ______|______
   |             |
   |  Harmonogram-|
   |   owanie    |
   |  dostępu do |
   |   pamięci   |
   |_____________|
          |
    ______|______
   |             |
   |  Kompresja  |
   |_____________|
          |
    ______|______
   |             |
   |Wirtualizacja|
   |   pamięci   |
   |_____________|
          |
        DRAM

Rysunek 5.5: Techniki efektywnego wykorzystania pamięci w systemie pamięci GPU.

  1. Koalescencja: Układanie dostępów do pamięci z wątków w warcie tak, aby dotyczyły one sąsiadujących lokalizacji pamięci, pozwalając sprzętowi na połączenie ich w pojedynczą, szerszą transakcję pamięci. Koalescencja maksymalizuje wykorzystanie przepustowości DRAM i zmniejsza liczbę transakcji pamięci.

    Przykład:

    // Wzorzec dostępu bez koalescencji
    int idx = threadIdx.x;
    float val = input[idx * stride];
     
    // Wzorzec dostępu z koalescencją
    int idx = threadIdx.x;
    float val = input[idx];
  2. Optymalizacja układu danych: Organizowanie struktur danych w pamięci w celu maksymalizacji lokalności przestrzennej i minimalizacji błędów cache'u. Obejmuje to techniki takie jak układ structure-of-arrays (SoA), który grupuje elementy danych tego samego typu razem, oraz układ array-of-structures (AoS).Here is the Polish translation of the provided markdown file, with the code comments translated but the code itself left unchanged:

Uporządkowanie, które przechowuje elementy danych należące do tej samej struktury razem.

Przykład:

// Układ Tablicy Struktur (Array-of-Structures, AoS)
struct Point {
    float x;
    float y;
    float z;
};
Point points[N];
 
// Układ Struktury Tablic (Structure-of-Arrays, SoA)
struct Points {
    float x[N];
    float y[N];
    float z[N];
};
Points points;
  1. Pamięć podręczna i Prefetchowanie: Efektywne wykorzystanie sprzętowo zarządzanej pamięci podręcznej poprzez wykorzystanie lokalności czasowej i przestrzennej w wzorcach dostępu do pamięci. Można to osiągnąć poprzez techniki takie jak segmentacja danych, która dzieli dane na mniejsze fragmenty, które mieszczą się w pamięci podręcznej, oraz prefetchowanie programowe, które jawnie ładuje dane do pamięci podręcznej przed ich potrzebą.

    Przykład:

    // Segmentacja danych
    for (int i = 0; i < N; i += TILE_SIZE) {
        for (int j = 0; j < N; j += TILE_SIZE) {
            // Przetwarzaj segment danych, który mieści się w pamięci podręcznej
            for (int ii = i; ii < i + TILE_SIZE; ii++) {
                for (int jj = j; jj < j + TILE_SIZE; jj++) {
                    // Wykonaj obliczenia na A[ii][jj]
                }
            }
        }
    }
  2. Harmonogram Dostępu do Pamięci: Przepraszanie dostępów do pamięci w celu maksymalizacji trafień w buforze wiersza i minimalizacji obciążenia związanego z przeładowywaniem i aktywacją pamięci DRAM. Można to zrobić za pomocą mechanizmów sprzętowych w kontrolerze pamięci lub poprzez techniki programowe, takie jak optymalizacja wzorca dostępu i transformacje układu danych.

  3. Kompresja: Zastosowanie technik kompresji danych w celu zmniejszenia rozmiaru danych przesyłanych między pamięcią a rdzeniami GPU. Może to pomóc w złagodzeniu wąskiego gardła przepustowości i zmniejszeniu zużycia energii związanego z przesyłaniem danych.

    Przykład:

    • Kodowanie różnicowe: Przechowywanie różnic między kolejnymi wartościami zamiast rzeczywistych wartości.
    • Kodowanie długości powtórzeń: Zastępowanie powtarzających się wartości pojedynczym wystąpieniem i licznikiem.
    • Kodowanie Huffmana: Przypisywanie krótszych ciągów bitów do wartości pojawiających się częściej.
  4. Hierarchia Pamięci****Wirtualizacja: Zastosowanie technik pamięci wirtualnej w celu zapewnienia ujednoliconej, ciągłej przestrzeni adresowej dla aplikacji GPU. Pozwala to na bardziej elastyczne zarządzanie pamięcią i umożliwia funkcje takie jak stronicowanie na żądanie, które mogą pomóc w zmniejszeniu wykorzystania pamięci i poprawie wydajności systemu.

Przykład:

  • Ujednolicone Adresowanie Wirtualne (Unified Virtual Addressing, UVA) w CUDA: Umożliwia wątkom GPU bezpośredni dostęp do pamięci CPU za pomocą pojedynczego wskaźnika, upraszczając zarządzanie pamięcią w systemach heterogenicznych.

Wieloczipowe Moduły GPU

Ponieważ wymagania dotyczące wydajności i zużycia energii przez GPU stale rosną, tradycyjne projekty z pojedynczym chipem mogą nie być w stanie dotrzymać tempa. Projekty wieloczipowe (Multi-Chip-Module, MCM), w których kilka chipów GPU jest zintegrowanych w jednym pakiecie, pojawiły się jako obiecujące rozwiązanie tego problemu.

Projekty wieloczipowych GPU oferują kilka korzyści:

  1. Wyższa przepustowość pamięci: Poprzez integrację wielu stosu lub chipów pamięci, wieloczipowe GPU mogą zapewnić znacznie wyższą przepustowość pamięci w porównaniu z projektami z pojedynczym chipem.

  2. Lepsza skalowalność: Projekty wieloczipowe umożliwiają integrację większej liczby jednostek obliczeniowych i kontrolerów pamięci, pozwalając GPU osiągać wyższe poziomy wydajności.

  3. Lepsza wydajność produkcji i opłacalność: Mniejsze indywidualne chipy w projekcie wieloczipowym mogą mieć lepsze wydajność produkcyjną i być bardziej opłacalne w porównaniu z dużymi monolitycznymi chipami.

Jednak projekty wieloczipowych GPU wprowadzają również nowe wyzwania, takie jak:

  1. Komunikacja międzyczipowa: Wydajna komunikacja między różnymi chipami w pakiecie wieloczipowym jest kluczowa dla wydajności. Wymagane są szybkie, niskoopóźnieniowe połączenia międzyczipowe, aby zminimalizować narzut związany z ruchem danych między chipami.

  2. Dostarczanie zasilania i zarządzanie cieplne: Projekty wieloczipowe wymagają starannych strategii dostarczania zasilania i zarządzania cieplnego, aby zapewnić optymalną wydajność i niezawodność.

  3. Wsparcie oprogramowania: Wieloczipowe GPU mogą wymagać zmian w modelu programowania i systemach uruchomieniowych, aby w pełni wykorzystać korzyści wynikające z architektury wieloczipowej.

Badania w tejPoniżej znajdziesz tłumaczenie na język polski tego pliku Markdown. Uwaga: kod nie jest tłumaczony, a jedynie komentarze.

To obszar bada nad projektowaniem i optymalizacją wieloczipowych układów GPU, w tym architektury systemu pamięci, projektu połączenia i zarządzania zasobami.

Na przykład Arunkumar i in. [2017] proponują projekt wieloczipowego układu GPU, który wykorzystuje szerokopasmowe, niskotaktowe połączenie do łączenia wielu układów GPU. Autorzy proponują również architekturę systemu pamięci, która wykorzystuje zwiększoną przepustowość i pojemność projektu wieloczipowego w celu poprawy wydajności i efektywności energetycznej.

Innym przykładem jest praca Milicia i in. [2018], która proponuje schemat zarządzania zasobami dla wieloczipowych układów GPU, mający na celu poprawę wykorzystania zasobów i zmniejszenie nadmiernej komunikacji między układami. Schemat wykorzystuje kombinację technik sprzętowych i programowych do monitorowania wykorzystania zasobów i wzorców komunikacji aplikacji oraz podejmowania dynamicznych decyzji o alokacji zasobów.

Wniosek

System pamięci jest kluczowym elementem nowoczesnych architektur GPU, a jego projekt i optymalizacja mają znaczący wpływ na całkowitą wydajność i efektywność systemu. Wraz ze wzrostem wymagań obciążeń równoległych, badacze eksplorują szeroki zakres technik w celu poprawy wydajności, skalowalności i adaptacyjności systemów pamięci GPU.

Niektóre z kluczowych kierunków badawczych w tej dziedzinie obejmują: harmonogramowanie dostępu do pamięci i projekt połączeń, efektywność pamięci podręcznej, priorytetyzacja żądań pamięci i ominięcie pamięci podręcznej, wykorzystanie heterogeniczności między wątkami, skoordynowane ominięcie pamięci podręcznej, adaptacyjne zarządzanie pamięcią podręczną, priorytetyzacja pamięci podręcznej, rozmieszczenie stron pamięci wirtualnej, rozmieszczenie danych i projekty wieloczipowe.

Eksplorując te i inne techniki, badacze dążą do opracowania systemów pamięci GPU, które będą mogły nadążyć za rosnącymi wymaganiami obciążeń równoległych, zachowując jednocześnie wysoką wydajność i efektywność energetyczną. Wraz z rozwojem GPU i ich wykorzystaniem w obszarach takich jak uczenie maszynowe, obliczenia naukowe i analityka danych, projektowanie i optymalizacja ich systemów pamięci będą miały kluczowe znaczenie.Here is the Polish translation of the provided text, with the code comments translated:

Przetwarzanie języka naturalnego pozostanie ważną dziedziną badań i innowacji.

# Importujemy niezbędne moduły
import spacy
from spacy.lang.en.stop_words import STOP_WORDS
 
# Ładujemy model języka angielskiego
nlp = spacy.load("en_core_web_sm")
 
# Definiujemy funkcję do obsługi tekstu
def process_text(text):
    # Tokenizujemy tekst
    doc = nlp(text)
    
    # Usuwamy stopwords
    tokens = [token.text for token in doc if token.text.lower() not in STOP_WORDS]
    
    # Zwracamy listę tokenów
    return tokens
 
# Przykładowy tekst do przetworzenia
sample_text = "Przetwarzanie języka naturalnego pozostanie ważną dziedziną badań i innowacji."
 
# Przetwarzamy tekst
processed_text = process_text(sample_text)
print(processed_text)