Modele językowe (LLM Basics) – Lab 1

Laboratorium 01A · fundamenty LLM

LLM nie czyta tekstu: tokenizacja, kontekst i pierwszy model

Najpierw przygotujesz repozytorium i środowisko, które będzie rozwijane przez cały kurs. Zanim poprosimy model o używanie narzędzi albo zbudujemy RAG, musimy zobaczyć, co naprawdę trafia do modelu. W tej części przejdziemy drogę od tekstu do identyfikatorów tokenów, policzymy rozmiar wejścia i uruchomimy ten sam kontrakt w trybie API oraz lokalnym.

Najpierw zbudujesz intuicję, potem uruchomisz eksperyment, a na końcu zapiszesz krótki rezultat.


Czego nauczysz się w tej części?

  • utworzyć jedno repozytorium i środowisko używane przez cały kurs
  • zainstalować zależności oraz sprawdzić kompletność środowiska
  • wyjaśnić, że model przyczynowy przewiduje rozkład następnego tokenu
  • odróżnić tekst, token i identyfikator tokenu
  • sprawdzić tokenizację tego samego tekstu w dwóch językach
  • wyjaśnić, co zajmuje okno kontekstu
  • uruchomić minimalne wywołanie API albo model lokalny
  • nie umieszczać klucza API w kodzie ani repozytorium
  • zapisać czas i zużycie tokenów bez logowania sekretów
  • traktować nazwę modelu jako konfigurację

Nie musisz jeszcze: znać macierzy uwagi (ang. attention matrix), trenować modelu ani rozumieć wszystkich parametrów biblioteki transformers. Dzisiaj interesuje nas granica między tekstem użytkownika a wejściem numerycznym modelu.

Jak będziemy pracować?

  • Krok 1repozytorium, środowisko i zależności całego projektu
  • Krok 2model LLM jako predyktor kolejnego tokenu
  • Krok 3eksperyment z tokenizatorem
  • Krok 4okno kontekstu, limity i koszt
  • Krok 5pierwsze wywołanie API lub modelu lokalnego
  • Krok 6wspólny format wyniku i pomiar
  • Krok 7rezultaty pracy i zapis wyniku
Zanim zaczniesz

W tym laboratorium zakładasz jeden projekt, który będzie rozwijany do końca kursu. Nie twórz osobnego środowiska dla każdego kolejnego ćwiczenia. W następnych tygodniach do tego samego repozytorium dodasz narzędzia, RAG, testy, API oraz raport końcowy.

Potrzebujesz Pythona 3.11 lub nowszego. Wybierz jeden dostępny tryb pracy: API z kluczem zapisanym w pliku .env albo mały model lokalny. Jeśli komputer nie ma dość pamięci, pierwsze kroki możesz wykonać ze stubem (uproszczoną, tymczasową wersją kodu, udającą prawdziwe działanie) – nie zaczynaj zajęć od wielogodzinnego pobierania wag.

Zanim uruchomisz tokenizer, zapisz własną odpowiedź na pytanie: „Ile tokenów ma słowo nieprzewidywalność?”. Dopiero potem sprawdź wynik. Bez wskazania konkretnego tokenizera to pytanie nie ma jednej poprawnej odpowiedzi.


Jedno repozytorium i jedno środowisko na cały kurs

Projekt kursowy budujemy przyrostowo. Dzisiejszy tokenizer i backend staną się później częścią aplikacji z narzędziami, wyszukiwaniem dokumentów, testami oraz interfejsem API. Wspólne repozytorium pozwoli rozwijać ten sam kod, a plik z wersjami zależności ułatwi odtworzenie środowiska przed oddaniem projektu.

Utwórz katalog projektu i środowisko wirtualne

W terminalu wykonaj poniższe polecenia. Nazwę katalogu możesz zmienić, ale używaj jej konsekwentnie przez cały kurs.

mkdir llm-course-project
cd llm-course-project
python -m venv .venv

# Windows PowerShell:
.\.venv\Scripts\Activate.ps1

# Linux lub macOS:
source .venv/bin/activate

python -m pip install --upgrade pip

Po aktywacji środowiska polecenia python i pip powinny wskazywać katalog .venv. Jeżeli system używa nazwy python3, zastosuj ją także przy tworzeniu środowiska.

Zależności całego projektu

Utwórz plik requirements.txt. Zawiera pakiety potrzebne nie tylko dzisiaj, lecz także w dalszych laboratoriach: do modeli lokalnych, wyszukiwania, testów i wystawienia API.

openai
python-dotenv
pydantic>=2,<3
numpy
pandas
torch
transformers
sentence-transformers
faiss-cpu
rank-bm25
fastapi
uvicorn
httpx
pytest
google-genai
python -m pip install -r requirements.txt
python -m pip freeze > requirements-lock.txt

Plik requirements.txt opisuje bezpośrednie potrzeby projektu, a requirements-lock.txt zapisuje dokładny stan działającego środowiska. Jeżeli na kursie otrzymasz przetestowany plik z wersjami, potraktuj go jako źródło nadrzędne i nie aktualizuj pakietów w połowie ćwiczeń bez powodu.

Instalacja PyTorch zależy od systemu i sposobu użycia GPU. Jeśli chcesz korzystać z CUDA, wybierz polecenie z oficjalnego konfiguratora PyTorch. Nie instaluj losowych plików wheel znalezionych w sieci. Gdy faiss-cpu nie jest dostępny na Twojej platformie, skorzystaj ze środowiska kursowego albo z wyszukiwania dokładnego w NumPy do czasu laboratorium o RAG.

Szkielet, który będzie rósł razem z projektem

Utwórz poniższą strukturę. Na razie część katalogów będzie pusta; w kolejnych laboratoriach dodasz do pakietu moduły narzędzi, retrievalu, ewaluacji i API.

llm-course-project/
├── src/
│   └── llm_course/
│       ├── __init__.py
│       ├── config.py
│       └── backends.py
├── data/
│   └── .gitkeep
├── tests/
│   └── .gitkeep
├── reports/
│   └── .gitkeep
├── logs/
│   └── .gitkeep
├── .env
├── .env.template
├── .gitignore
├── check_environment.py
├── pyproject.toml
├── requirements.txt
├── requirements-lock.txt
└── README.md

Przy układzie z katalogiem src dodaj minimalny pyproject.toml. Dzięki temu pakiet llm_course będzie można importować z testów i skryptów bez ręcznego zmieniania PYTHONPATH.

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "llm-course-project"
version = "0.1.0"
requires-python = ">=3.11"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
python -m pip install -e .

W pliku README.md zapisz na początek: cel projektu, wymaganą wersję Pythona, sposób instalacji, wybór trybu local/api, polecenie uruchomienia testów oraz listę znanych ograniczeń. Aktualizuj tę instrukcję po każdym laboratorium – pod koniec kursu nie będziesz odtwarzać jej z pamięci.

Konfiguracja i sekrety

Do repozytorium dodaj .env.template z nazwami zmiennych, ale bez prawdziwych sekretów. Następnie skopiuj go lokalnie jako .env i uzupełnij wyłącznie na swoim komputerze. Tryb local pozwala rozpocząć bez płatnego API.

# .env.template - ten plik trafia do repozytorium
LLM_MODE=local
GEMINI_API_KEY=
GEMINI_MODEL=
OPENAI_API_KEY=
OPENAI_MODEL=
LOCAL_MODEL=Qwen/Qwen2.5-0.5B-Instruct
DATA_DIR=data
LOG_DIR=logs
# Windows PowerShell:
Copy-Item .env.template .env

# Linux lub macOS:
cp .env.template .env

Dodaj również .gitignore:

.venv/
.env
__pycache__/
*.py[cod]
.pytest_cache/
logs/*
!logs/.gitkeep
data/private/

Po przypadkowym opublikowaniu klucza samo usunięcie go z ostatniego commita nie wystarcza. Klucz należy unieważnić u dostawcy, wygenerować nowy i sprawdzić historię repozytorium.

Sprawdź środowisko, zanim zaczniesz pisać aplikację

Zapisz poniższy skrypt jako check_environment.py. Dzięki niemu szybko odróżnisz brak pakietu od błędu w kodzie laboratorium.

from importlib.util import find_spec
import platform
import sys

MODULES = [
    "dotenv",
    "openai",
    "pydantic",
    "numpy",
    "pandas",
    "torch",
    "transformers",
    "sentence_transformers",
    "faiss",
    "rank_bm25",
    "fastapi",
    "uvicorn",
    "httpx",
    "pytest",
    "google-genai"
]

missing = [name for name in MODULES if find_spec(name) is None]

print("Python:", sys.version.split()[0])
print("System:", platform.platform())

if find_spec("torch"):
    import torch

    print("PyTorch:", torch.__version__)
    print("CUDA dostępna:", torch.cuda.is_available())

if missing:
    print("Brakujące moduły:", ", ".join(missing))
    raise SystemExit(1)

print("Środowisko jest gotowe.")
python check_environment.py
python check_environment.py > environment.txt
git init
git status --short

Plik environment.txt dołącz do pierwszego checkpointu. Przed pierwszym commitem sprawdź wynik git status: plik .env nie może się na nim pojawić.

Co jest gotowe po tym kroku?

Masz bazę projektu odpowiadającą wymaganiom końcowym: miejsce na kod aplikacji, dane, testy, logi i raporty; dwa tryby modelu; jawny zestaw zależności; oraz konfigurację bez sekretów w repozytorium. Kolejne laboratoria będą rozszerzać tę strukturę, a nie zastępować ją nowymi projektami.

Co właściwie robi model językowy?

W uproszczeniu przyczynowy model językowy otrzymuje dotychczasową sekwencję tokenów i przypisuje prawdopodobieństwo każdemu tokenowi, który może pojawić się jako następny. Po wybraniu jednego tokenu dopisuje go do sekwencji i powtarza operację. Długa odpowiedź powstaje więc krok po kroku, a nie przez jednorazowe wyszukanie gotowego akapitu w bazie.

Co znaczy wzór?

W tym zapisie t1, …, tn oznaczają kolejne tokeny, a n – długość sekwencji. Wyrażenie P(ti | t1, …, ti−1) oznacza prawdopodobieństwo przypisane tokenowi na pozycji i, gdy znane są wcześniejsze tokeny. Symbol ∏ mówi, że mnożymy te prawdopodobieństwa dla wszystkich pozycji. Wynikiem jest prawdopodobieństwo całej konkretnej sekwencji według modelu.

Iloczyn można liczyć krok po kroku. Jeśli pierwszy token ma prawdopodobieństwo 0,5, a drugi – pod warunkiem wystąpienia pierwszego – 0,2, to prawdopodobieństwo sekwencji tych dwóch tokenów wynosi 0,5 × 0,2 = 0,1. Prawdopodobieństwo trzeciego wybranego tokenu mnożymy przez ten dotychczasowy wynik.

Gdyby na drugiej pozycji wybrać inny token, o prawdopodobieństwie 0,3, powstałaby inna sekwencja o prawdopodobieństwie 0,5 × 0,3 = 0,15. Model przewidywałby następny token na podstawie tej nowej sekwencji. Wcześniejsze tokeny wpływają więc na prawdopodobieństwa kolejnych tokenów, a iloczyn pozwala obliczyć prawdopodobieństwo całej wybranej sekwencji.

To wyjaśnia kilka zachowań modeli: odpowiedź może być płynna, lecz fałszywa; mała zmiana początku może zmienić dalszą kontynuację; model nie zna automatycznie bieżących danych; a wygenerowany tekst nie jest dowodem, że model wykonał obliczenie albo sprawdził źródło.

Model nie pracuje na słowach i nie przechowuje zdań jak wyszukiwarka. Jego bezpośrednim zadaniem jest przewidywanie tokenów. Wiedza, styl i wiele umiejętności ujawniają się pośrednio dzięki wzorcom wyuczonym w parametrach.

Tekst staje się identyfikatorami tokenów

Tokenizer ma słownik oraz reguły podziału tekstu. Tokenem może być całe krótkie słowo, fragment słowa, znak interpunkcyjny, spacja zakodowana razem z fragmentem albo część zapisu emoji. Ten sam tekst może zostać podzielony inaczej przez dwa modele.

WarstwaPrzykładCo widzi kolejny etap
tekstModele uczą się.ciąg znaków Unicode
tokenyzależne od tokenizerafragmenty należące do słownika
token IDsnp. [42, 817, 19]liczby całkowite
embeddingiwektory liczb rzeczywistychreprezentacje wejściowe sieci

Obejrzyj tokeny zamiast o nich zgadywać

Uruchom poniższy eksperyment. Model jest celowo mały; korzystamy przede wszystkim z jego tokenizera. Zmień listę przykładów tak, aby zawierała Twoje nazwisko, fragment kodu, liczbę, emoji oraz zdanie z polskimi znakami.

from transformers import AutoTokenizer

MODEL_NAME_V1 = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer_v1 = AutoTokenizer.from_pretrained(MODEL_NAME_V1)
MODEL_NAME_V2 = "bert-base-cased"
tokenizer_v2 = AutoTokenizer.from_pretrained(MODEL_NAME_V2)

samples = [
    "kot",
    "nieprzewidywalność",
    "Large language model",
    "Duży model językowy",
    "print('Hello!')",
    "LLM-y 🤖 kosztują 0.01 USD",
]

for name, tokenizer in [(MODEL_NAME_V1, tokenizer_v1), (MODEL_NAME_V2, tokenizer_v2)]:
    print("\n", name)
    for text in samples:
        ids = tokenizer.encode(text, add_special_tokens=False)
        pieces = [tokenizer.decode([token_id]) for token_id in ids]
        print(f"\nTEXT:   {text!r}")
        print(f"IDS:    {ids}")
        print(f"PIECES: {pieces}")
        print(f"COUNT:  {len(ids)}")

  • Czy liczba tokenów jest równa liczbie słów?
  • Jak tokenizer traktuje spacje i znaki interpunkcyjne?
  • Czy polska i angielska wersja zdania mają tyle samo tokenów?
  • Czy decode(encode(text)) odtwarza tekst? Sprawdź to jawnie.
Jak interpretować wynik?

Nie szukaj jednej liczby poprawnej dla wszystkich modeli. Wynik zależy od tokenizera i jego wersji. Najważniejszy wniosek brzmi: token nie jest synonimem słowa, a koszty i limity trzeba liczyć tokenizatorem właściwym dla modelu albo na podstawie pola usage zwróconego przez dostawcę.

Okno kontekstu nie jest pamięcią bez dna

Do limitu kontekstu wchodzą wszystkie elementy przekazane modelowi: instrukcje systemowe lub deweloperskie, historia rozmowy, pytanie użytkownika, fragmenty RAG, definicje narzędzi, wyniki narzędzi i zwykle także generowana odpowiedź. Dokładny sposób rozliczania zależy od API i modelu.

Więcej kontekstu nie zawsze oznacza lepszą odpowiedź. Może zwiększyć koszt i czas jej przygotowania, wprowadzić sprzeczne informacje oraz utrudnić odnalezienie tego, co istotne. Dlatego w dalszej części będziemy mierzyć jakość wyszukiwania fragmentów (retrieval) i kontrolować liczbę tokenów przekazywanych modelowi.

Policz budżet, nie cenę z pamięci

Napisz funkcję obliczającą koszt na podstawie liczby tokenów oraz cen podanych w konfiguracji. Ceny podawaj jako koszt miliona tokenów. Nie wpisuj do materiałów stawek zapamiętanych z Internetu – zmieniają się.

def estimate_cost_usd(
    input_tokens: int,
    output_tokens: int,
    input_usd_per_million: float,
    output_usd_per_million: float,
) -> float:
    return (
        input_tokens / 1_000_000 * input_usd_per_million
        + output_tokens / 1_000_000 * output_usd_per_million
    )

print(estimate_cost_usd(12_000, 800, 0.0, 0.0))

Najbliższym prawdy źródłem liczby tokenów w trybie API jest pole usage odpowiedzi dostawcy. Lokalny tokenizer daje pomiar dokładny dla lokalnego modelu, ale nie musi odpowiadać tokenizacji innego modelu.

Jeden kontrakt, dwa tryby pracy

Kod aplikacji nie powinien wiedzieć, czy tekst generuje usługa zewnętrzna, czy model na komputerze. Na razie wystarczy wspólny obiekt wyniku. Umieść kontrakt i implementacje w src/llm_course/backends.py. Dzięki temu kolejne laboratoria nie będą uzależnione od jednego dostawcy.

from dataclasses import dataclass
from typing import Protocol


@dataclass
class Generation:
    text: str
    input_tokens: int | None
    output_tokens: int | None
    latency_s: float
    model: str
    mode: str


class LLMBackend(Protocol):
    def generate(self, system: str, user: str, max_tokens: int = 160) -> Generation:
        ...

Minimalny backend API

import os
import time

class ApiBackend:
    def __init__(self) -> None:
        from google import genai
        # OpenAI: from openai import OpenAI

        self.model = os.getenv("GEMINI_MODEL", "gemini-2.5-flash-lite")
        self.client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
        # OpenAI: self.model = os.environ["OPENAI_MODEL"]
        # OpenAI: self.client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

    def generate(self, system: str, user: str, max_tokens: int = 160) -> Generation:
        from google.genai import types

        started = time.perf_counter()
        response = self.client.models.generate_content(
            model=self.model,
            contents=user,
            config=types.GenerateContentConfig(
                system_instruction=system,
                max_output_tokens=max_tokens,
            ),
        )
        # OpenAI:
        # response = self.client.responses.create(
        #     model=self.model,
        #     instructions=system,
        #     input=user,
        #     max_output_tokens=max_tokens,
        # )
        latency_s = time.perf_counter() - started

        generated_text = response.text
        # OpenAI: generated_text = response.output_text
        if not generated_text:
            raise RuntimeError("API nie zwróciło tekstu odpowiedzi")

        # OpenAI:
        # if response.status != "completed":
        #     raise RuntimeError(
        #         f"Generowanie nie zostało zakończone: "
        #         f"{response.status}, {response.incomplete_details}"
        #     )

        usage = response.usage_metadata
        # OpenAI: usage = response.usage
        return Generation(
            text=generated_text,
            input_tokens=getattr(usage, "prompt_token_count", None),
            output_tokens=getattr(usage, "candidates_token_count", None),
            # OpenAI: input_tokens=getattr(usage, "input_tokens", None),
            # OpenAI: output_tokens=getattr(usage, "output_tokens", None),
            latency_s=latency_s,
            model=self.model,
            mode="api",
        )    

Minimalny backend lokalny

import os
import time

class LocalBackend:
    def __init__(self) -> None:
        import torch
        from transformers import AutoModelForCausalLM, AutoTokenizer

        self.torch = torch
        self.model_name = os.getenv("LOCAL_MODEL", "Qwen/Qwen2.5-0.5B-Instruct")
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
        self.model = AutoModelForCausalLM.from_pretrained(
            self.model_name,
            dtype="auto",
        )
        self.model.to(self.device).eval()

    def generate(self, system: str, user: str, max_tokens: int = 160) -> Generation:
        started = time.perf_counter()
        messages = [
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ]
        inputs = self.tokenizer.apply_chat_template(
            messages,
            tokenize=True,
            add_generation_prompt=True,
            return_dict=True,
            return_tensors="pt",
        ).to(self.device)

        with self.torch.inference_mode():
            output = self.model.generate(
                **inputs,
                max_new_tokens=max_tokens,
                do_sample=False,
                pad_token_id=self.tokenizer.eos_token_id,
            )
        input_tokens = int(inputs["input_ids"].shape[1])
        new_ids = output[0, input_tokens:]
        generated_text = self.tokenizer.decode(new_ids, skip_special_tokens=True)
        latency_s = time.perf_counter() - started

        return Generation(
            text=generated_text,
            input_tokens=input_tokens,
            output_tokens=int(new_ids.shape[0]),
            latency_s=latency_s,
            model=self.model_name,
            mode="local",
        )

Jeżeli dany tokenizer nie ma szablonu rozmowy, aplikacja powinna mieć kontrolowany fallback. Nie sklejaj przypadkowo napisów typu [SYSTEM], jeśli dokumentacja modelu wymaga innego formatu – format rozmowy jest częścią kontraktu modelu.

To samo pytanie, dwa wykonania

Wyślij to samo pytanie do dostępnego backendu API i lokalnego. Nie oceniaj jeszcze, który model jest „mądrzejszy”. Porównaj format odpowiedzi, liczbę tokenów, czas oraz zachowanie przy limicie 30 i 160 tokenów.

import os

def make_backend() -> LLMBackend:
    mode = os.getenv("LLM_MODE", "local")
    if mode == "local":
        return LocalBackend()
    if mode == "api":
        return ApiBackend()
    raise ValueError("LLM_MODE musi mieć wartość 'local' albo 'api'")


if __name__ == "__main__":
    import dotenv
    dotenv.load_dotenv()
    backend = make_backend()
    result = backend.generate(
        system="Odpowiadaj krótko i jasno po polsku.",
        user="Wyjaśnij, czym jest token w modelu językowym.",
        max_tokens=160,
    )

    print(result.text)
    print(
        f"\nModel: {result.model} | tryb: {result.mode} | "
        f"wejście: {result.input_tokens} tokenów | "
        f"wyjście: {result.output_tokens} tokenów | "
        f"czas: {result.latency_s:.2f} s"
    )

Jeśli nie masz klucza API, wykonaj ćwiczenia na modelu lokalnym lub stubie. Tryb API możesz obejrzeć na wspólnej demonstracji i dodać później – opanowanie materiału nie powinno zależeć od dostępu do płatnej usługi.

Loguj zdarzenie, nie całą prywatną rozmowę

Na tym etapie wystarczy pojedynczy rekord JSONL zawierający czas, model, tryb, liczbę tokenów i status. Surowy prompt może zawierać dane osobowe albo sekret, dlatego nie powinien domyślnie trafiać do logów.

import json
from dataclasses import asdict
from datetime import datetime, timezone

def append_event(path: str, result: Generation, status: str = "ok") -> None:
    event = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "status": status,
        **{k: v for k, v in asdict(result).items() if k != "text"},
    }
    with open(path, "a", encoding="utf-8") as file:
        file.write(json.dumps(event, ensure_ascii=False) + "\n")

Praca z AI: możesz poprosić narzędzie o wyjaśnienie nieznanego parametru albo wygenerowanie klasy pomocniczej. Potem wskaż w kodzie: skąd biorą się tokeny wejścia, gdzie kończy się prompt, jak odcinane są tokeny odpowiedzi i dlaczego tekst nie jest logowany.

Rezultat po tej części

Oddaj repozytorium projektu kursowego. Powinno zawierać:

  • strukturę src/llm_course, data, tests, reports i logs,
  • pliki requirements.txt oraz requirements-lock.txt albo przetestowany plik wersji udostępniony na kursie,
  • pyproject.toml i krótkie README.md z instrukcją odtworzenia środowiska,
  • pliki .env.template i .gitignore; lokalny .env nie może być śledzony przez Git,
  • wynik skryptu kontrolnego zapisany jako environment.txt,
  • plik lab01a.json z nazwą tokenizera i wynikami dla co najmniej pięciu różnych tekstów,
  • jedno wywołanie modelu z trybem, czasem i liczbą tokenów,
  • krótką odpowiedź: dlaczego liczby tokenów nie można wiarygodnie oszacować liczbą słów,
  • krótkie potwierdzenie, że to repozytorium będzie rozwijane w kolejnych laboratoriach.

Podsumowanie

Co naprawdę warto zapamiętać?

  • jedno repozytorium i jedno środowisko rozwijasz przez cały kurs
  • requirements.txt opisuje potrzeby projektu, a plik lock dokładny działający stan
  • LLM przyczynowy przewiduje następny token warunkowo względem poprzednich
  • token nie jest tym samym co słowo
  • tokenizacja zależy od modelu i jego tokenizera
  • instrukcje, historia, RAG i narzędzia zajmują wspólne okno kontekstu
  • tryb API oraz lokalny powinny realizować wspólny kontrakt
  • plik .env.template może trafić do repozytorium, lecz .env z sekretami nie

Najpierw ustal, co rzeczywiście trafia do modelu i co z niego wraca. Dopiero potem oceniaj jakość odpowiedzi.

Co możesz sprawdzić w dokumentacji lub z pomocą AI?

  • dokładną nazwę i dostępność modelu na danym koncie
  • aktualną cenę miliona tokenów
  • maksymalny kontekst i format rozmowy konkretnego modelu
  • szczegóły konfiguracji urządzenia w PyTorch
  • zgodność pakietu faiss-cpu z używanym systemem i wersją Pythona

Pytania kontrolne

1. Dlaczego przez cały kurs rozwijamy jedno repozytorium i jedno środowisko?
2. Czym różni się requirements.txt od pliku z dokładnie zablokowanymi wersjami?
3. Dlaczego .env.template śledzimy w Git, a plik .env ignorujemy?
4. Dlaczego dwa tokenizatory mogą podzielić ten sam tekst inaczej?
5. Które elementy aplikacji zużywają okno kontekstu?
6. Dlaczego pole usage z API jest lepsze niż heurystyka liczby znaków?
7. Po co aplikacji wspólny interfejs dla modelu lokalnego i API?
8. Jakie informacje warto logować, a jakich domyślnie nie zapisywać?

Materiały dodatkowe

Materiał przygotowany dla kursu „Modele językowe (LLM Basics)”. Stan techniczny: wrzesień 2026. Nazwy modeli i ceny zawsze sprawdzaj w aktualnej dokumentacji dostawcy.


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *