Navigácia
AI

Lokálne doladenie LLM na Macu s MLX a LoRA: návod bez cloudového GPU

1 novembra, 2026 techy.sk

Malý jazykový model sa dá doladiť priamo na Macu s čipom Apple Silicon, bez prenájmu cloudového GPU. Pomáha mu k tomu framework MLX a technika LoRA. Zadanie však sľubuje viac, než je férne: MLX „nedrví“ PyTorch a doladenie model nenaučí spoľahlivo „nikdy neponúkať React“. Tento návod ukazuje, čo reálne dosiahneš, ako pripraviť dáta zo svojich projektov a spustiť trénovanie.

Čo je overené a čo nie

  • „MLX drví PyTorch“: nepodložené tvrdenie. Porovnania závisia od modelu, čipu a nastavení a nenašiel som preň jednoznačný, všeobecne platný doklad. Na vlastnom Macu si ich môžeš porovnať sám.
  • Kopírovanie medzi RAM a VRAM: na Apple Silicon sa pamäť zdieľa medzi CPU a GPU, takže „klasické“ kopírovanie do samostatnej grafickej pamäte tam v tom zmysle neexistuje. Ani PyTorch s backendom MPS preto nezačína s týmto handicapom. Výhoda MLX je v tom, že je od začiatku navrhnutý na tento model pamäte a pracuje s poľami bez presunov medzi zariadeniami.
  • Trend „koncom roka 2026“: doklad o masívnom presune vývojárov k lokálnemu dolaďovaniu som nenašiel.
  • Čipy: MLX funguje na všetkých čipoch Apple Silicon (M1 a novšie). Na Macu s procesorom Intel nefunguje.

Čo je unifikovaná pamäť

Na bežnom PC majú procesor a grafická karta oddelené pamäte. Dáta sa musia medzi nimi presúvať. Pri čipoch Apple Silicon majú CPU aj GPU prístup do tej istej pamäte. Pre dolaďovanie modelu to znamená, že limitom je celková pamäť stroja, nie samostatná kapacita grafickej karty. Pre menšie modely (jednotky miliárd parametrov) to stačí na rozumné experimenty. Odporúčané minimum je podľa dostupných návodov 16 GB, pohodlnejšie 32 GB.

Čo je LoRA

Pri plnom dolaďovaní by si upravoval všetky váhy modelu. LoRA pôvodné váhy zmrazí a k vybraným vrstvám pridá malé trénovateľné „adaptéry“. Trénuje sa len zlomok parametrov (často pod jedno percento), takže sú pamäťové nároky aj veľkosť výsledku výrazne menšie. Adaptér je malý súbor, ktorý sa dá načítať k základnému modelu alebo s ním zlúčiť. Variant QLoRA používa navyše kvantizovaný (stlačený) základný model, čo šetrí pamäť.

Čo doladenie dokáže a čo nie

  • Dokáže: posunúť štýl, formát výstupu a bežné vzory (napríklad pomenovanie, štruktúra súborov, preferencia čistého DOM kódu).
  • Nedokáže spoľahlivo: pridať nové znalosti ani zaručiť, že model nikdy neponúkne React. Menší model môže stále halucinovať.
  • Lacnejšia alternatíva: ak ide len o to, aby model nepoužíval framework, často stačí systémový prompt alebo súbor s pravidlami projektu. Doladenie má zmysel, ak chceš štýl zapečený do modelu alebo bežať celé lokálne.

Krok 1: Inštalácia

python3 -m venv .venv
source .venv/bin/activate
pip install -U mlx-lm
# ak trénovanie nahlási chýbajúce závislosti:
pip install -U "mlx-lm[train]"

Krok 2: Dataset z vlastných projektov

Najdôležitejšia časť je kvalita dát. Model sa učí z príkladov, takže potrebuješ páry „zadanie a kód“. Skript nižšie prejde priečinok s tvojimi projektmi, preskočí súbory, ktoré vyzerajú ako tajomstvá, a vytvorí súbory train.jsonl a valid.jsonl vo formáte chatových správ. Zadanie sa odvodí z úvodného komentára v súbore alebo z jeho názvu. Takýto popis je len hrubý, takže výsledok nebude dokonalý. Čím lepšie zadania napíšeš, tým lepšie model dostaneš.

import json
import random
import re
from pathlib import Path

SRC = Path("~/projekty").expanduser()
OUT = Path("data")
OUT.mkdir(exist_ok=True)

EXT = {".js", ".html", ".css"}
SKIP_DIRS = {"node_modules", ".git", "dist", "build", "vendor"}
MIN_CHARS, MAX_CHARS = 200, 6000
SYSTEM = ("Si programátor, ktorý píše čistý Vanilla JavaScript a Tailwind CSS "
          "bez frameworkov. Odpovedaj iba kódom.")

SECRET = re.compile(
    r"(api[_-]?key|secret|token|password)s*[:=]s*['"][^'"]+['"]", re.I
)


def describe(path: Path, code: str) -> str:
    m = re.match(r"s*(?:<!--|/*|//)s*(.+?)s*(?:-->|*/)?s*(?:n|$)", code)
    return m.group(1) if m else f"súbor {path.name}"


rows = []
for p in SRC.rglob("*"):
    if not p.is_file() or p.suffix not in EXT:
        continue
    if any(part in SKIP_DIRS for part in p.parts):
        continue
    code = p.read_text(encoding="utf-8", errors="ignore")
    if not (MIN_CHARS < len(code) < MAX_CHARS) or SECRET.search(code):
        continue
    rows.append({"messages": [
        {"role": "system", "content": SYSTEM},
        {"role": "user", "content": f"Napíš kód: {describe(p, code)}"},
        {"role": "assistant", "content": code},
    ]})

random.seed(42)
random.shuffle(rows)
n_valid = max(1, len(rows) // 10)
valid, train = rows[:n_valid], rows[n_valid:]

for name, part in (("train", train), ("valid", valid)):
    with open(OUT / f"{name}.jsonl", "w", encoding="utf-8") as f:
        for r in part:
            f.write(json.dumps(r, ensure_ascii=False) + "n")

print(f"Trénovacích: {len(train)}, validačných: {len(valid)}")

Pozor na dáta: použi len kód, ktorý je tvoj alebo ku ktorému máš práva. Skript odfiltruje len očividné tajomstvá, takže pred trénovaním dáta prejdi aj očami. Menej kvalitných príkladov je lepšie než veľa chaotických.

Krok 3: Trénovanie

mlx_lm.lora 
  --model mlx-community/Llama-3.2-3B-Instruct-4bit 
  --train 
  --data ./data 
  --iters 600 
  --learning-rate 1e-4 
  --mask-prompt

Voľba --mask-prompt nastaví, aby sa chyba počítala len z odpovede asistenta, nie zo zadania. Model v príklade je len ukážka. Skontroluj licenciu zvoleného modelu a či je k dispozícii v MLX formáte. Pamäťové nároky a čas závisia od veľkosti modelu a dĺžky príkladov. Ak ti dochádza pamäť, skráť príklady alebo zmenši model. Adaptéry sa uložia do priečinka adapters.

Krok 4: Vyskúšaj a porovnaj

from mlx_lm import load, generate

MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit"

def ask(adapter_path, task):
    model, tokenizer = load(MODEL, adapter_path=adapter_path)
    messages = [
        {"role": "system", "content": "Si programátor, ktorý píše čistý Vanilla JavaScript a Tailwind CSS bez frameworkov. Odpovedaj iba kódom."},
        {"role": "user", "content": task},
    ]
    prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    return generate(model, tokenizer, prompt=prompt, max_tokens=500)

task = "Napíš kód: tlačidlo, ktoré prepína tmavý režim"
print("--- základný model ---")
print(ask(None, task))
print("--- s adaptérom ---")
print(ask("adapters", task))

Porovnávaj na zadaniach, ktoré model počas trénovania nevidel. Pozri sa, či sa pridrží požadovaného štýlu, a či stále ponúka framework. Ak sa chceš zbaviť samostatného adaptéra, môžeš ho zlúčiť do modelu: mlx_lm.fuse --model … --adapter-path adapters --save-path fused-model.

Bežné chyby

  • Pretrénovanie: model si zapamätá tvoje súbory a prestane zovšeobecňovať. Sleduj chybu na validačných dátach a nespúšťaj zbytočne veľa iterácií.
  • Málo alebo chaotické dáta: výsledok bude rovnako chaotický.
  • Zabudnutý systémový prompt: pri použití modelu používaj rovnaký formát správ ako pri trénovaní.
  • Očakávanie zázraku: trojmiliardový model sa nepomeria s veľkými modelmi, len ho posunieš k svojmu štýlu.

Čo si z toho odniesť

Lokálne doladenie malého modelu na Macu je dostupné a poučné. Reálnym vstupom je dataset, nie framework. Ak ide o preferenciu štýlu kódu, najprv skús systémový prompt, a doladenie nechaj na prípad, keď chceš lokálny model s tvojím štýlom zabudovaným dovnútra.

Zdieľať článok

Mohlo by vás zaujímať