Przygotowanie do rozmów rekrutacyjnych z LLM i Generatywnej AI

Pytania Rekrutacyjne dla Inżynierów LLM i Generatywnej AI

15 wybranych pytań rekrutacyjnych z LLM i generatywnej AI pogrupowanych według poziomu doświadczenia. Użyj ich, aby powtórzyć podstawy, praktyczne kompromisy i rozumowanie na poziomie seniorskim dotyczące produkcji.

Rozpocznij wywiad AI z LLM i Generatywnej AIKarta kredytowa nie jest wymagana. Dostępna 1 darmowa sesja.
Trening technicznych rozmów rekrutacyjnych po angielskuTryb dla osób, dla których angielski nie jest językiem ojczystym, do ćwiczenia rozmów technicznych.

Pytania dla Juniorów

1Wyjaśnij tokenizację podwyrazową i dlaczego jest ona preferowana nad tokenizacją na poziomie słów lub znaków we współczesnych modelach językowych.

Tokenizacja podwyrazowa (subword tokenization) to hybrydowe podejście do segmentacji tekstu, które dzieli tekst na fragmenty morfologiczne o zmiennej długości lub podciągi oparte na częstotliwości (takie jak 'un', 'break', 'able'), zamiast całych słów lub pojedynczych znaków. Algorytmy takie jak Byte-Pair Encoding (BPE), WordPiece i Unigram LM uczą się słownictwa o stałym rozmiarze z korpusu treningowego, gdzie częste słowa pozostają nienaruszone jako pojedyncze tokeny, natomiast rzadkie lub nieznane słowa są rozkładane na znane jednostki podwyrazowe. Tokenizacja podwyrazowa jest preferowana we współczesnych modelach językowych, ponieważ równoważy rozmiar słownictwa, długość sekwencji i odporność na słowa spoza słownika (out-of-vocabulary, OOV). Czysta tokenizacja na poziomie słów wymaga nadmiernie dużego słownictwa (prowadzącego do ogromnych macierzy osadzeń) i nadal cierpi z powodu tokenów OOV mapowanych na ogólne tokeny '[UNK]'. Z kolei czysta tokenizacja na poziomie znaków eliminuje problemy OOV, ale generuje bardzo długie sekwencje, które drastycznie zwiększają złożoność obliczeniową w mechanizmach uwagi (które skalują się kwadratowo z długością sekwencji) i rozcieńczają gęstość semantyczną na token. Tokenizacja podwyrazowa osiąga optymalny kompromis, utrzymując długości sekwencji na rozsądnym poziomie, praktyczne rozmiary słownictwa (zazwyczaj od 32 tys. do 128 tys. tokenów) oraz zerowe wskaźniki OOV (zwłaszcza w połączeniu z rezerwowymi mechanizmami na poziomie bajtów).

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('gpt2')
text = 'unbelievable'
tokens = tokenizer.tokenize(text)
token_ids = tokenizer.encode(text)

print('Tokens:', tokens)
print('Token IDs:', token_ids)
Odpowiedz na to pytanie z trenerem AI

2Wyjaśnij różnicę między statycznym osadzaniem słów (static word embeddings), osadzaniem kontekstowym (contextual embeddings) i ukrytymi stanami transformera (transformer hidden states).

Statyczne osadzanie słów (static word embeddings), osadzanie kontekstowe (contextual embeddings) i ukryte stany transformera (transformer hidden states) reprezentują postępującą ewolucję w sposobie, w jaki reprezentacje tekstu przechwytują znaczenie i kontekst składniowy. 1. **Statyczne osadzanie słów** (np. Word2Vec, GloVe, FastText) przypisuje jeden, stały wektor każdemu tokenowi słownika, niezależnie od jego kontekstu zdania. W tym paradygmacie słowa polisemiczne, takie jak 'bank' (brzeg rzeki vs. bank finansowy) lub 'apple' (owoc vs. firma technologiczna), mają identyczne reprezentacje wektorowe we wszystkich kontekstach, polegając na statycznej tablicy wyszukiwania (lookup table). 2. **Osadzanie kontekstowe** (np. wczesne ELMo, reprezentacje tokenów BERT lub osadzanie zdań z Bi-Encoderów) tworzy reprezentacje, w których wektor dla tokenu jest dynamiczną funkcją jego otaczającego kontekstu. W BERT lub ELMo, 'bank' w 'river bank' otrzymuje zupełnie inny wektor osadzenia niż 'bank' w 'deposit money at the bank'. 3. **Ukryte stany transformera** odnoszą się do pośrednich reprezentacji wektorowych generowanych na każdej pojedynczej warstwie sieci transformera podczas przejścia w przód (forward pass). Biorąc pod uwagę wektory osadzeń tokenów wejściowych na warstwie 0, każda kolejna warstwa transformera stosuje mechanizm uwagi własnej (self-attention) i transformacje typu feed-forward, produkując sekwencję wektorów stanu ukrytego `h_l` na warstwie `l`. Podczas gdy stany ukryte ostatniej warstwy działają jako wysokopoziomowe osadzanie kontekstowe, niższe i środkowe stany ukryte przechwytują niskopoziomowe cechy składniowe, leksykalne i strukturalne. W ten sposób ukryte stany transformera obejmują pełne pionowe kontinuum reprezentacji warstwa po warstwie w całej sieci.

import torch
from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModel.from_pretrained('bert-base-uncased', output_hidden_states=True)

text1 = 'River bank'
text2 = 'Bank deposit'

inputs1 = tokenizer(text1, return_tensors='pt')
inputs2 = tokenizer(text2, return_tensors='pt')

with torch.no_grad():
    out1 = model(**inputs1)
    out2 = model(**inputs2)

# Layer 0 (Input token embeddings - static lookup before self-attention)
static_bank_1 = out1.hidden_states[0][0, 2] # 'bank'
# Layer 12 (Final contextual hidden state after all attention layers)
contextual_bank_1 = out1.hidden_states[12][0, 2]
contextual_bank_2 = out2.hidden_states[12][0, 1]

print('Cosine similarity of bank in different contexts (Layer 12):',
      torch.cosine_similarity(contextual_bank_1, contextual_bank_2, dim=0).item())
Odpowiedz na to pytanie z trenerem AI

3Wyjaśnij, co reprezentuje przestrzeń osadzeń (embedding space) i jak interpretowane jest podobieństwo cosinusowe dla osadzeń tekstu.

Przestrzeń osadzeń (embedding space) to ciągła, wysokowymiarowa przestrzeń wektorowa R^d, w której dyskretne jednostki tekstowe (słowa, zdania lub dokumenty) są mapowane w taki sposób, że podobieństwa semantyczne, syntaktyczne lub relacyjne odpowiadają bliskości geometrycznej i relacjom kierunkowym. W tej przestrzeni odległości i kąty odzwierciedlają związki semantyczne. Podobieństwo cosinusowe mierzy cosinus kąta theta między dwoma wektorami u i v, obliczany jako: `Cosine Similarity(u, v) = (u . v) / (||u|| ||v||)` Podobieństwo cosinusowe jest interpretowane w osadzeniach tekstu w następujący sposób: - Zakres i Orientacja: Daje wartość skalarną, zazwyczaj ograniczoną w przedziale [-1, 1] (lub [0, 1] dla nieujemnych osadzeń). Wartość bliska 1.0 wskazuje, że dwa wektory wskazują praktycznie w tym samym kierunku, odzwierciedlając wysokie podobieństwo semantyczne lub zgodność tematyczną. Wartość bliska 0.0 oznacza ortogonalność (niezależność semantyczną lub brak powiązań), a wartości ujemne wskazują na przeciwne orientacje. - Niezmienniczość względem długości wektora (Magnitude Invariance): W przeciwieństwie do odległości euklidesowej (odległości L2) lub iloczynu skalarnego, podobieństwo cosinusowe normalizuje względem długości wektora. W osadzeniach tekstu długość wektora (vector magnitude) może czasami korelować z długością sekwencji, częstotliwością tokenów lub specyficznością terminu. Koncentrując się wyłącznie na dopasowaniu kierunkowym, podobieństwo cosinusowe izoluje orientację semantyczną od różnic w długości wektorów.

import numpy as np

def cosine_sim(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Hypothetical 3D embeddings
v_king = np.array([0.9, 0.1, 0.4])
v_queen = np.array([0.85, 0.15, 0.42])
v_apple = np.array([0.1, 0.9, -0.2])

print('Sim(king, queen):', round(cosine_sim(v_king, v_queen), 4))
print('Sim(king, apple):', round(cosine_sim(v_king, v_apple), 4))
Odpowiedz na to pytanie z trenerem AI

4Wyjaśnij różnicę między osadzeniami tokenów (token embeddings), osadzeniami pozycyjnymi (positional embeddings) oraz osadzeniami segmentów lub typów (segment or type embeddings) w danych wejściowych transformatora.

W danych wejściowych transformatora (szczególnie w architekturach w stylu BERT) reprezentacja wejściowa dla każdego tokena jest zazwyczaj tworzona przez sumowanie element po elemencie trzech odrębnych wektorów osadzeń: 1. **Osadzenia tokenów (Token Embeddings)**: Mapują dyskretne identyfikatory tokenów słownika na gęste wektory, reprezentujące podstawową tożsamość semantyczną i leksykalną tokenów. 2. **Osadzenia pozycyjne (Positional Embeddings)**: Wprowadzają do reprezentacji informacje o kolejności tokenów i indeksie sekwencyjnym, kompensując fakt, że mechanizm uwagi własnej (self-attention) jest z natury niezmienny względem permutacji. 3. **Osadzenia segmentów (lub typów tokenów) (Segment or Token Type Embeddings)**: Rozróżniają różne fragmenty tekstu lub zdania spakowane w jedną sekwencję wejściową (takie jak Zdanie A kontra Zdanie B w zadaniach klasyfikacji par lub odpowiadania na pytania). Połączenie tych osadzeń zapewnia pojedynczy gęsty tensor wejściowy, kodujący znaczenie tokena, pozycję i grupowanie sekwencji, zanim zostanie przekazany do pierwszej warstwy transformatora.

import torch
import torch.nn as nn

vocab_size, max_seq_len, num_segments, d_model = 30522, 512, 2, 768
tok_embed = nn.Embedding(vocab_size, d_model)
pos_embed = nn.Embedding(max_seq_len, d_model)
seg_embed = nn.Embedding(num_segments, d_model)

input_ids = torch.tensor([[101, 7592, 102, 2023, 102]]) # Token IDs
type_ids = torch.tensor([[0,   0,    0,   1,    1  ]]) # Segment IDs (Sentence A vs B)
positions = torch.arange(input_ids.size(1)).unsqueeze(0)   # Indices: [0, 1, 2, 3, 4]

# Final representation is the element-wise sum
input_rep = tok_embed(input_ids) + pos_embed(positions) + seg_embed(type_ids)
print(input_rep.shape)
Odpowiedz na to pytanie z trenerem AI

5Wyjaśnij uwaga (attention) jako mechanizm do powiązywania tokenów w sekwencji, włączając zapytania (queries), klucze (keys), wartości (values) i uwagę wielogłowicową (multi-head attention).

Uwaga (attention) to mechanizm, który pozwala tokenom w sekwencji dynamicznie kierować informacje i ważyć istotność wszystkich innych tokenów na podstawie dopasowania kontekstowego. Liniowe projekcje przekształcają wejście każdego tokena w trzy wektory: - **Zapytanie (Q):** Reprezentuje informacje, których szuka bieżący token. - **Klucz (K):** Reprezentuje atrybuty lub treść, którą token oferuje do dopasowania z zapytaniami. - **Wartość (V):** Zawiera rzeczywisty ładunek informacyjny do zagregowania. W uwadze skalowanej iloczynem skalarnym (scaled dot-product attention), wyniki uwagi są obliczane poprzez pomnożenie Zapytań i Kluczy ($Q K^T$), skalowane przez $\frac{1}{\sqrt{d_k}}$, aby zapobiec zanikaniu gradientu w dużych wymiarach, a następnie normalizowane funkcją softmax. Ostateczne wyjście to ważona suma Wartości: $$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$ Uwaga wielogłowicowa (Multi-Head Attention – MHA) rzutuje $Q$, $K$ i $V$ na wiele niezależnych podprzestrzeni reprezentacyjnych (głów) równolegle. Pozwala to modelowi jednocześnie zwracać uwagę na różne typy relacji (np. strukturę składniową, koreferencję, zależności dalekiego zasięgu) w różnych pozycjach. Wyjścia głów są konkatenowane i liniowo rzutowane z powrotem do wymiaru modelu.

import torch
import torch.nn.functional as F

# Q, K, V: [batch_size, seq_len, head_dim]
Q = torch.randn(1, 4, 64)
K = torch.randn(1, 4, 64)
V = torch.randn(1, 4, 64)
d_k = Q.size(-1)

scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)

print("Output shape:", output.shape)
Odpowiedz na to pytanie z trenerem AI

6Wyjaśnij, jak maskowanie uwagi (attention masking) różni się w przypadku dekoderów przyczynowych (causal decoders) a dwukierunkowych koderów (bidirectional encoders) i jakie zachowania umożliwia lub im zapobiega.

Maskowanie uwagi (attention masking) kontroluje, którym tokenom wolno zwracać uwagę na inne tokeny, ustawiając logity uwagi (wyniki przed funkcją softmax) na $-\infty$ dla niedozwolonych par, zapewniając, że ich waga uwagi po funkcji softmax wynosi dokładnie 0. 1. **Dekodery Przyczynowe (causal decoders) (np. GPT, LLaMA):** Używają dolnotrójkątnej maski przyczynowej (autoregresywnej). Token na pozycji $i$ może zwracać uwagę tylko na pozycje $j \le i$. Zapobiega to zwracaniu uwagi na przyszłe tokeny, umożliwiając autoregresywną generację token po tokenie podczas wnioskowania i zapobiegając wyciekowi etykiet przyszłych tokenów podczas równoległego treningu. 2. **Dwukierunkowe Kodery (bidirectional encoders) (np. BERT):** Nie używają maski przyczynowej; każdy token może zwracać uwagę na wszystkie przeszłe i przyszłe tokeny w całej sekwencji. Używają masek dopełniających (padding masks), aby zapobiec zwracaniu uwagi przez ważne tokeny na puste tokeny `[PAD]` w sekwencjach przetwarzanych wsadowo. Dwukierunkowa uwaga (bidirectional attention) tworzy bogate, wszechstronne reprezentacje kontekstowe, idealne do zadań związanych ze zrozumieniem, ale uniemożliwia bezpośrednią, jednoprzebiegową autoregresywną generację tekstu.

import torch
import torch.nn.functional as F

scores = torch.randn(3, 3)
# Create upper-triangular mask for future positions
causal_mask = torch.triu(torch.ones(3, 3, dtype=torch.bool), diagonal=1)

# Mask future positions with -inf before softmax
masked_scores = scores.masked_fill(causal_mask, float('-inf'))
atten_weights = F.softmax(masked_scores, dim=-1)
print(atten_weights)
Odpowiedz na to pytanie z trenerem AI

7Wyjaśnij różnicę między architekturami transformatorowymi typu tylko-koder (encoder-only), tylko-dekoder (decoder-only) i koder-dekoder (encoder-decoder) dla zadań językowych.

Trzy podstawowe architektury transformatorowe różnią się zasadniczo wzorcami maskowania uwagi i docelowymi celami operacyjnymi: 1. Tylko-koder (Encoder-Only) (np. BERT, RoBERTa): Używa dwukierunkowej autouwagi, gdzie każdy token może odnosić się do wszystkich innych tokenów w sekwencji jednocześnie. Produkuje bogate reprezentacje kontekstowe dla całej sekwencji wejściowej, co czyni go idealnym do klasyfikacji, ekstraktywnego odpowiadania na pytania (QA) i reprezentacji cech. Nie potrafi naturalnie generować tekstu autoregresywnego. 2. Tylko-dekoder (Decoder-Only) (np. GPT-3, Llama, Mistral): Używa kauzalnej (jednokierunkowej) autouwagi, gdzie token $i$ może odnosić się tylko do tokenów na pozycjach $j \le i$. Jest trenowany autoregresywnie za pomocą predykcji następnego tokenu i służy jako standardowa architektura dla generatywnych modeli językowych, generowania kodu i otwartych konwersacji. 3. Koder-dekoder (Encoder-Decoder) (np. T5, BART): Łączy dwukierunkowy koder z autoregresyjnym dekoderem kauzalnym. Oprócz kauzalnej autouwagi nad wygenerowanymi tokenami, dekoder używa warstw krzyżowej uwagi (cross-attention), które odpytują reprezentacje wyjściowe kodera. Ta architektura jest specjalnie zaprojektowana do zadań transformacji sekwencji na sekwencję, takich jak tłumaczenie i sumaryzacja.

# Causal mask (Decoder-Only) vs Full mask (Encoder-Only)
import torch

seq_len = 4
encoder_mask = torch.ones(seq_len, seq_len)  # Full bidirectional attention
decoder_causal_mask = torch.tril(torch.ones(seq_len, seq_len))  # Lower-triangular

print("Encoder Mask:
", encoder_mask)
print("Decoder Causal Mask:
", decoder_causal_mask)
Odpowiedz na to pytanie z trenerem AI

Pytania dla Midów

8Wyjaśnij, jak wybór tokenizera – pod kątem poziomu bajtowego, uwzględniania Unicode i wielojęzyczności – wpływa na jakość modelu, koszty i uczciwość międzylinguistyczną.

Wybory projektowe tokenizera – takie jak segmentacja na poziomie bajtów vs. uwzględniająca Unicode oraz alokacja słownictwa wielojęzycznego – bezpośrednio wpływają na jakość modelu w dalszych etapach, koszt inferencji/treningu oraz uczciwość lingwistyczną. Pod względem kosztów i uczciwości, tokenizery szkolone głównie na korpusach anglojęzycznych lub opartych na piśmie łacińskim, przydzielają większość wpisów słownictwa angielskim słowom i morfemom. W konsekwencji język angielski osiąga wysoką kompresję (np. ~1,3 tokena na słowo), podczas gdy języki spoza pisma łacińskiego (np. arabski, dewanagari, tajski, chiński) lub języki o małych zasobach są często fragmentowane na wiele podpamięci lub surowych bajtów UTF-8 (często 3 do 6 tokenów na słowo). Ta dysproporcja jest często nazywana „podatkiem tokenowym” lub „nierównowagą w gęstości tokenów”: użytkownicy innych języków niż angielski płacą znacznie więcej za jednostkę treści semantycznej w rozliczeniach API, zużywają limity okna kontekstowego znacznie szybciej i cierpią na wyższe opóźnienia. Pod względem jakości, tokenizery na poziomie bajtowym (takie jak Byte-level BPE w GPT-2/GPT-4 lub SentencePiece z powrotem do bajtów w LLaMA) całkowicie unikają awarii z powodu nieznanych znaków i błędów wyjścia poza słownik (UNK), ponieważ każdy prawidłowy ciąg UTF-8 rozkłada się na tokeny bajtowe. Jednak nadmierne fragmentowanie bajtów obniża jakość reprezentacji, ponieważ transformer musi poświęcić warstwy na rekombinowanie fragmentów bajtów w koncepcje semantyczne, zanim wykona rozumowanie na wysokim poziomie. Zwiększenie rozmiaru słownictwa wielojęzycznego (np. rozszerzenie z 32 tys. do 128 tys.+ tokenów) równoważy gęstość tokenów i poprawia wydajność zadań w dalszych etapach w różnych językach, kosztem umiarkowanie większej warstwy osadzania wejścia/wyjścia.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('gpt2')

english_text = 'Hello world'
hindi_text = 'नमस्ते दुनिया'

print('English tokens:', tokenizer.tokenize(english_text))
print('Hindi tokens:', tokenizer.tokenize(hindi_text))
print('English token count:', len(tokenizer.encode(english_text)))
print('Hindi token count:', len(tokenizer.encode(hindi_text)))
Odpowiedz na to pytanie z trenerem AI

9Jakie artefakty tokenizacji pojawiają się w rozumowaniu numerycznym, generowaniu kodu lub rzadkim tekście Unicode i jak wyspecjalizowane tokenizatory mogą je zmniejszyć?

Artefakty tokenizacji pojawiają się, gdy tokenizatory podwyrazowe niespójnie dzielą tekst strukturalny, numeryczny lub rzadki, uniemożliwiając modelowi rozpoznanie podstawowej regularności semantycznej lub składniowej. Główne artefakty to: 1. Artefakty rozumowania numerycznego: Standardowe tokenizatory BPE (Byte Pair Encoding) szkolone na ogólnym tekście dzielą liczby na arbitralne fragmenty o różnej długości w oparciu o ich częstotliwość (np. '12345' może zostać stokenizowany jako ['12', '345'], podczas gdy '12346' jako ['123', '46']). To niespójne grupowanie zaburza wyrównanie wartości pozycyjnych (cyfry, dziesiątki, setki) i utrudnia rozumowanie arytmetyczne. 2. Artefakty generowania kodu: Wcięcia (początkowe spacje/tabulatory) i operatory wieloznakowe (np. '==', '!=', '->') często są dzielone w sposób nieregularny na granicy znaków i spacji, co prowadzi do błędów wcięć, zawyżonej liczby tokenów w głęboko wciętym kodzie i uszkodzeń składni. 3. Artefakty rzadkich znaków Unicode i emotikon (Emoji): Wielobajtowe sekwencje UTF-8 (takie jak złożone emotikony z łącznikami o zerowej szerokości znaku (zero-width joiners) lub rzadkie skrypty) są dzielone na surowe tokeny bajtowe, które nie niosą indywidualnego znaczenia semantycznego, powodując halucynacje znaków lub uszkodzone renderowanie glifów podczas generacji. Wyspecjalizowane tokenizatory zmniejszają te artefakty, stosując dostosowane reguły pre-tokenizacji i ograniczenia słownictwa: - Dzielenie cyfr: Wymuszanie tokenizacji pojedynczych cyfr (np. dzielenie każdej cyfry `0-9` za pomocą wyrażeń regularnych na oddzielny token) zapewnia jednolitą reprezentację wartości pozycyjnych dla rozumowania matematycznego. - Dedykowane tokeny białych znaków/wcięć: Dodawanie jawnych tokenów dla wcięć wielospacjowych (np. 2, 4, 8 spacji) i zachowanie słów kluczowych/operatorów języka programowania. - Pre-tokenizacja za pomocą wyrażeń regularnych / awaryjne tokeny bajtowe: Używanie rozdzielaczy opartych na wyrażeniach regularnych (takich jak wyrażenia regularne z GPT-4/tiktoken), które rozdzielają interpunkcję, litery i liczby na ścisłe kategorie, zanim zostaną obliczone połączenia BPE, zapobiegając łączeniu międzykategoriowemu (np. zapobiegając połączeniu 'a=10' w jeden token).

import tiktoken

# tiktoken cl100k_base (GPT-4 / ChatGPT) enforces digit and whitespace handling
enc = tiktoken.get_encoding('cl100k_base')

num1 = '12345'
num2 = '12346'
code_indent = '    def foo():'

print('Tokens num1:', [enc.decode([t]) for t in enc.encode(num1)])
print('Tokens num2:', [enc.decode([t]) for t in enc.encode(num2)])
print('Tokens code:', [enc.decode([t]) for t in enc.encode(code_indent)])
Odpowiedz na to pytanie z trenerem AI

10Zastanów się nad kompromisami dotyczącymi uwagi dla długich kontekstów (long-context attention), w tym uwagi pełnej o złożoności kwadratowej (quadratic full attention), uwagi z oknem przesuwnym (sliding-window attention), uwagi rzadkiej lub globalnej (sparse or global attention), kosztu pamięci podręcznej kluczy-wartości (KV-cache cost) oraz rozcieńczenia uwagi (attention dilution).

Skalowanie uwagi (attention) do długich okien kontekstowych (context windows) wiąże się z kompromisami w zakresie obliczeń, zajmowanej pamięci i wierności modelu: 1. **Uwaga pełna o złożoności kwadratowej (Quadratic Full Attention) a uwaga z oknem przesuwnym (Sliding-Window) / uwaga rzadka (Sparse Attention):** Standardowa uwaga pełna (full attention) skaluje się kwadratowo ($O(N^2)$) pod względem obliczeń i pamięci aktywacji (activation memory) wraz z długością sekwencji $N$. Uwaga z oknem przesuwnym (local attention) ogranicza uwagę do ustalonego sąsiedztwa $W$, redukując złożoność do $O(N \cdot W)$, ale wymaga wielu warstw do propagacji informacji między odległymi tokenami. Rzadkie lub globalne wzorce uwagi (sparse or global attention patterns) łączą lokalne okna z wybranymi globalnymi tokenami kotwiczącymi, aby utrzymać skalowanie $O(N)$, jednocześnie umożliwiając komunikację na dalekie odległości. 2. **Koszt pamięci podręcznej kluczy-wartości (KV-cache Memory Cost):** Podczas generowania autoregresywnego, klucze (keys) i wartości (values) dla wszystkich poprzednich tokenów są buforowane, aby uniknąć zbędnych obliczeń. Pamięć podręczna KV (KV-cache) skaluje się liniowo z długością sekwencji ($O(B \cdot L \cdot H_{KV} \cdot D \cdot N)$). Dla bardzo długich kontekstów (32 tys. – 128 tys. i więcej tokenów) pamięć podręczna KV zużywa dziesiątki gigabajtów pamięci VRAM GPU na partię (batch), co stanowi wąskie gardło dla maksymalnego rozmiaru partii i przepustowości pamięci. 3. **Rozcieńczenie uwagi (Attention Dilution) (Lost-in-the-Middle):** W miarę wzrostu kontekstu, mianownik funkcji softmax (softmax denominator) sumuje się po dziesiątkach tysięcy tokenów, rozpraszając masę prawdopodobieństwa cienko po nieistotnym kontekście. Ten wzrost entropii rozcieńcza ostrość uwagi, pogarszając zdolność modelu do wiarygodnego przypominania sobie konkretnych informacji osadzonych w środku długich podpowiedzi (prompts).

def kv_cache_gb(batch_size, seq_len, layers=32, kv_heads=8, head_dim=128, bytes_per_elem=2):
    # 2 for Key and Value
    total_bytes = batch_size * seq_len * layers * kv_heads * head_dim * 2 * bytes_per_elem
    return total_bytes / (1024 ** 3)

print(f"32k context: {kv_cache_gb(4, 32768):.2f} GB")
print(f"128k context: {kv_cache_gb(4, 131072):.2f} GB")
Odpowiedz na to pytanie z trenerem AI

11Porównaj MHA, MQA i GQA oraz wyjaśnij, jak wpływają one na pamięć podręczną KV (KV-cache) i przepustowość dekodowania.

Multi-Head Attention (MHA), Multi-Query Attention (MQA) i Grouped-Query Attention (GQA) różnią się sposobem współdzielenia głowic klucza ($K$) i wartości ($V$) przez głowice zapytania ($Q$): 1. **Multi-Head Attention (MHA)**: Ma równą liczbę głowic $Q$, $K$ i $V$ ($H_Q = H_{KV}$, stosunek 1:1). Każda głowica zapytania odwołuje się do własnych, niezależnych reprezentacji klucza/wartości. Choć jest to podejście ekspresyjne, wymaga buforowania odrębnych macierzy KV dla każdej głowicy. 2. **Multi-Query Attention (MQA)**: Wykorzystuje wiele głowic $Q$ ($H$), ale tylko 1 współdzieloną głowicę $K$ i 1 współdzieloną głowicę $V$ (stosunek $H:1$). Zmniejsza to rozmiar pamięci podręcznej KV o współczynnik $H$, ale może prowadzić do niewielkiej utraty jakości lub niestabilności treningu. 3. **Grouped-Query Attention (GQA)**: Grupuje głowice $Q$ w $G$ partycji, gdzie każda grupa współdzieli pojedynczą głowicę $K$ i $V$ (np. 8 głowic $Q$ na jedną głowicę KV). GQA oferuje optymalny kompromis, odzyskując praktycznie całą jakość modelowania MHA, jednocześnie zachowując korzyści pamięciowe MQA. **Wpływ na pamięć podręczną KV i przepustowość dekodowania:** Autoregresywne generowanie tokenów (dekodowanie) jest ograniczone przepustowością pamięci, ponieważ GPU musi przesłać całą pamięć podręczną KV z pamięci HBM (High-Bandwidth Memory) do pamięci SRAM (Static Random-Access Memory) na układzie scalonym dla każdego generowanego tokena. Zmniejszając liczbę głowic KV o $H/G$ (np. $4\times$ do $8\times$ w GQA, lub $32\times+$ w MQA): - Zajmowana pamięć podręczna KV jest proporcjonalnie zmniejszona, co umożliwia znacznie większe rozmiary partii obsługujących żądania (serving batch sizes) w pamięci VRAM (Video RAM) GPU. - Ruch odczytu pamięci HBM na token znacznie spada, drastycznie zwiększając przepustowość tokenów dekodowania.

# Model with 32 Query Heads
num_q_heads = 32

mha_kv_heads = 32 # 1:1 ratio
gqa_kv_heads = 8  # 4:1 ratio (4 query heads per KV head)
mqa_kv_heads = 1  # 32:1 ratio (1 shared KV head)

print(f"KV Cache Size Relative to MHA:")
print(f"MHA: {mha_kv_heads / mha_kv_heads * 100:.1f}%")
print(f"GQA: {gqa_kv_heads / mha_kv_heads * 100:.1f}%")
print(f"MQA: {mqa_kv_heads / mha_kv_heads * 100:.1f}%")
Odpowiedz na to pytanie z trenerem AI

12Wyjaśnij, jak FlashAttention przyspiesza dokładne obliczenia uwagi (attention computation), nie zmieniając wyników uwagi.

FlashAttention przyspiesza obliczenia uwagi, czyniąc algorytm świadomym operacji wejścia/wyjścia (IO-aware) — minimalizując ruch pamięci podczas odczytu i zapisu między wolną pamięcią GPU o wysokiej przepustowości (HBM - High Bandwidth Memory) a szybką pamięcią SRAM na chipie, zamiast próbować zmniejszyć całkowitą liczbę operacji zmiennoprzecinkowych (FLOP). Standardowa uwaga materializuje pośrednie macierze N x N wyników uwagi i prawdopodobieństwa w HBM, co powoduje poważne wąskie gardło przepustowości pamięci. FlashAttention pokonuje to dzięki trzem kluczowym mechanizmom: 1. **Kaflkowanie (Tiling):** Dzieli macierze Query, Key i Value na bloki, które w całości mieszczą się w pamięci SRAM na chipie GPU. 2. **Softmax online:** Oblicza softmax przyrostowo dla bloków, śledząc bieżące maksima i sumy normalizujące, aktualizując częściowe wyniki bez potrzeby przechowywania pełnej zmaterializowanej macierzy N x N w pamięci. 3. **Dokładne ponowne obliczanie:** Podczas przejścia wstecznego (backward pass) nie odczytuje przechowywanych pośrednich macierzy uwagi z HBM; zamiast tego, oblicza je ponownie na bieżąco w pamięci SRAM na podstawie przechowywanych statystyk bieżących. Ponieważ nie są używane żadne przybliżenia, faktoryzacje niskiego rzędu ani heurystyki odrzucania tokenów, wynik jest matematycznie dokładny z precyzją zmiennoprzecinkową, jednocześnie zmniejszając zużycie pamięci HBM z O(N^2) do O(N).

import torch

def online_softmax_step(m_prev, l_prev, out_prev, scores_block, v_block):
    # scores_block: (B, H, Br, Bc), v_block: (B, H, Bc, D)
    m_block = scores_block.max(dim=-1, keepdim=True).values
    m_new = torch.maximum(m_prev, m_block)
    
    # Rescale previous and current accumulators
    p_prev_scale = torch.exp(m_prev - m_new)
    p_block = torch.exp(scores_block - m_new)
    
    l_new = p_prev_scale * l_prev + p_block.sum(dim=-1, keepdim=True)
    out_new = (p_prev_scale * l_prev * out_prev + p_block @ v_block) / l_new
    return m_new, l_new, out_new
Odpowiedz na to pytanie z trenerem AI

Pytania dla Seniorów

13Zaprojektuj deterministyczne przepływy pracy oparte na agentach (agentic workflows) używając planistów, maszyn stanów, grafów acyklicznych (DAG), typowanego stanu pośredniego, ograniczonych ponowień (bounded retries) i weryfikacji wyników narzędzi, zamiast otwartych pętli agentów.

Otwarte pętle agentów (np. nieograniczone autonomiczne pętle ReAct) w środowisku produkcyjnym często cierpią na niedeterministyczne rozgałęzienia, nieskończone pętle, niekontrolowane zużycie tokenów i dryf stanu. Deterministyczny przepływ pracy oparty na agentach zastępuje swobodne pętle ustrukturyzowanym, obserwowalnym przepływem sterowania: 1. **Maszyny stanów i grafy acykliczne (DAG):** Przepływ sterowania jest definiowany jako jawny graf acykliczny (Directed Acyclic Graph – DAG) lub skończony automat stanów (np. LangGraph, Temporal, AWS Step Functions). Przejścia węzłów zależą od jawnych warunków i typowanych wyników, a nie od swobodnych decyzji modelu. 2. **Typowany stan pośredni:** Stan współdzielony między węzłami jest modelowany za pomocą ścisłych schematów (np. modeli Pydantic lub dataclasses). Węzły wykonują walidowane operacje odczytu i zapisu, zapobiegając dryfowi schematu lub nieprawidłowo sformułowanemu stanowi. 3. **Planisci:** Ustrukturyzowani planiści emitują z góry ograniczony plan (np. uporządkowaną listę kroków opartych na wyliczeniach) lub wybierają z ograniczonego zestawu prawidłowych przejść stanów, zamiast swobodnie decydować o kolejnych akcjach bez ograniczeń. 4. **Weryfikacja wyników narzędzi:** Wyniki zwracane przez narzędzia są deterministycznie walidowane względem schematów i reguł biznesowych przed aktualizacją stanu lub przekazaniem ich do kolejnych etapów dużego modelu językowego (Large Language Model – LLM). 5. **Ograniczone ponowienia (Bounded Retries) i mechanizmy awaryjne (Fallbacks):** Każdy krok wymusza jawne budżety ponowień, wykładnicze wycofywanie (exponential backoffs), limity czasu i przejścia awaryjne (np. eskalację do przeglądu przez człowieka lub wyzwolenie bezpiecznego wstrzymania), aby zagwarantować zakończenie.

from pydantic import BaseModel
from typing import Optional, Literal

class WorkflowState(BaseModel):
    user_query: str
    extracted_id: Optional[str] = None
    verification_status: Literal["PENDING", "VERIFIED", "FAILED"] = "PENDING"
    retry_count: int = 0
    max_retries: int = 3

def execute_validation_node(state: WorkflowState) -> WorkflowState:
    if state.retry_count >= state.max_retries:
        state.verification_status = "FAILED"
        return state
    try:
        result = call_verification_service(state.extracted_id)
        state.verification_status = "VERIFIED" if result.is_valid else "FAILED"
    except Exception:
        state.retry_count += 1
    return state
Odpowiedz na to pytanie z trenerem AI

14Zaprojektuj politykę pewności i wstrzymywania się od odpowiedzi dla asystenta LLM (Large Language Model) odpowiadającego na pytania w domenach regulowanych.

W domenach regulowanych (takich jak opieka zdrowotna, bankowość, prawo i zgodność) błędne odpowiedzi niosą za sobą kary regulacyjne, odpowiedzialność prawną i ryzyko bezpieczeństwa. Solidna polityka pewności i wstrzymywania się od odpowiedzi łączy kalibrowane punktowanie pewności z wielu sygnałów, stopniowane progi odpowiedzi oraz deterministyczne przepływy pracy eskalacji: 1. **Kalibracja pewności na podstawie wielu sygnałów**: Surowe logarytmy prawdopodobieństw (logprobs) LLM często są źle skalibrowane dla zapytań spoza domeny. Wynik pewności powinien syntetyzować wiele niezależnych sygnałów: * **Wynik ugruntowania wyszukiwania (Retrieval Grounding Score)**: Podobieństwo semantyczne i pewność ponownego rankingu pobranych fragmentów dowodów. * **Implikacja na poziomie twierdzenia (NLI - Natural Language Inference)**: Modele wnioskowania w języku naturalnym weryfikujące, czy każde wyodrębnione twierdzenie jest implikowane przez pobrany kontekst źródłowy. * **Entropia semantyczna / Spójność wewnętrzna (Self-Consistency)**: Mierzenie spójności semantycznej w wielu próbkowanych generacjach. * **Logarytmy prawdopodobieństw tokenów modelu (Model Token Logprobs)**: Minimalne i średnie logarytmy prawdopodobieństw dla kluczowych nazwanych encji i tokenów faktograficznych. 2. **Warstwowa polityka wstrzymywania się od odpowiedzi (Tiered Abstention Policy)**: * **Wysoka pewność (Wynik >= Wysoki Próg)**: Bezpośrednio dostarcz wygenerowaną odpowiedź z cytatami w tekście. * **Średnia pewność / Dwuznaczność (Niski Próg <= Wynik < Wysoki Próg)**: Dostarcz konserwatywną odpowiedź z wyraźnymi zastrzeżeniami, wyłączeniami odpowiedzialności lub poproś użytkownika o szczegóły doprecyzowujące. * **Niska pewność / Poza zakresem (Wynik < Niski Próg)**: Bezwzględne wstrzymanie się od odpowiedzi z ustandaryzowaną wiadomością odmowną. 3. **Eskalacja i audytowalność zgodności**: * **Deterministyczna eskalacja**: Wstrzymania od odpowiedzi lub krytyczne rozbieżności są automatycznie kierowane do kolejek z udziałem człowieka (HITL - Human-in-the-Loop) lub systemów zgłoszeń dla agentów z pełnym kontekstem. * **Ścieżka audytu i pochodzenie (Audit Trail & Lineage)**: Pełna telemetria – w tym hasze promptów, identyfikatory pobranych dokumentów, indywidualne wyniki składowych pewności i końcowe decyzje routingu – musi być rejestrowana dla celów audytowalności regulacyjnej.

from dataclasses import dataclass
from typing import Literal

@dataclass
class DecisionResult:
    action: Literal["SERVE", "SERVE_WITH_CAVEAT", "ABSTAIN_AND_ESCALATE"]
    confidence_score: float
    reason: str

def evaluate_confidence_policy(retrieval_score: float, nli_entailment_score: float, semantic_entropy: float) -> DecisionResult:
    # Composite calibrated confidence index [0, 1]
    composite_score = (0.4 * retrieval_score) + (0.4 * nli_entailment_score) + (0.2 * (1.0 - semantic_entropy))
    
    if composite_score >= 0.85:
        return DecisionResult("SERVE", composite_score, "High evidence grounding")
    elif composite_score >= 0.60:
        return DecisionResult("SERVE_WITH_CAVEAT", composite_score, "Partial evidence support")
    else:
        return DecisionResult("ABSTAIN_AND_ESCALATE", composite_score, "Insufficient ground truth")
Odpowiedz na to pytanie z trenerem AI

15Zaprojektuj strategię routingu modeli, która wybiera spośród małych, średnich i dużych modeli na podstawie złożoności żądania, kosztu, ryzyka i wymagań jakościowych.

Produkcyjna architektura routingu modeli kieruje przychodzące żądania do małych (np. 1B–8B SLM), średnich (np. modele 14B–70B) i dużych (np. modele "frontier" lub duże modele MoE) kategorii, równoważąc złożoność, opóźnienia, ryzyko i koszt obliczeń. Przepływ pracy routingu zazwyczaj łączy reguły statyczne, routing predykcyjny i dynamiczne kaskady awaryjne: 1. **Deterministyczne/Statyczne bramki polityki**: Filtrowanie żądań według kategorii klienta, rygorystycznych umów SLA (Service Level Agreement) dotyczących opóźnień, ryzyka regulacyjnego/domenowego (np. diagnoza medyczna lub sporządzanie dokumentów prawnych kierowane bezpośrednio do modeli najwyższego poziomu) lub prostych zadań dopasowanych do reguł (np. podstawowe wyrażenia regularne/formatowanie kierowane do małych modeli). 2. **Predykcyjny routing złożoności**: Szybki, lekki klasyfikator (taki jak wyszukiwanie podobieństwa osadzeń, kros-enkoder lub mały router SLM) ocenia złożoność żądania, głębokość rozumowania i dwuznaczność domeny, aby z góry wybrać najbardziej opłacalną kategorię. 3. **Dynamiczne kaskady wykonania i eskalacji**: Wyślij prompt najpierw do mniejszego modelu i oceń pewność wyjściową (za pomocą logarytmów prawdopodobieństw tokenów/entropii, poprawności schematu strukturalnego lub sprawdzeń zabezpieczeń). Jeśli pewność jest poniżej progu lub walidacja zakończy się niepowodzeniem, router eskaluje do modelu średniego lub dużego. Kluczowe kompromisy systemowe obejmują narzut opóźnienia routera w porównaniu z oszczędnościami obliczeniowymi, budżety limitów czasu dla mechanizmów awaryjnych podczas nagłych wzrostów ruchu oraz ciągłą ewaluację (np. ewaluacja w tle – shadow evaluation – w celu śledzenia dryftu jakości wyników w poszczególnych kategoriach).

class DynamicModelRouter:
    def __init__(self, small_client, medium_client, large_client, classifier, guardrail):
        self.small = small_client
        self.medium = medium_client
        self.large = large_client
        self.classifier = classifier
        self.guardrail = guardrail

    async def route_and_execute(self, request):
        # 1. Deterministic Risk Gate
        if request.risk_level == "high" or request.domain in ["legal", "medical_compliance"]:
            return await self.large.generate(request.prompt)
        
        # 2. Predictive Complexity Classifier
        complexity = self.classifier.predict_complexity(request.prompt) # 0.0 to 1.0
        
        if complexity < 0.35:
            response = await self.small.generate(request.prompt)
            if self.guardrail.is_acceptable(response):
                return response
            return await self.medium.generate(request.prompt) # Fallback
            
        if complexity < 0.75:
            response = await self.medium.generate(request.prompt)
            if self.guardrail.is_acceptable(response):
                return response
            return await self.large.generate(request.prompt) # Fallback
            
        # 3. High complexity frontier execution
        return await self.large.generate(request.prompt)
Odpowiedz na to pytanie z trenerem AI