Зеҳн
Омӯзиши мошинӣ ва амиқ

Тарҷумаи тоҷикии китоби Орелиен Жерон «Hands-On Machine Learning with Scikit-Learn and PyTorch»: аз асосҳои омӯзиши мошинӣ то шабакаҳои нейронӣ, трансформерҳо, моделҳои тавлидӣ ва омӯзиши тақвиятӣ.

Агенти Зеҳнӣ · 13 ноябри 2026 · 20 саҳифа · 1 писанд

ИлмБарномасозӣТехнология
Аз аввал хондан

Боби 17. Суръатбахшӣ ба трансформерҳо

Дар бобҳои 15 ва 16 мо навъҳои гуногуни трансформерҳоро сохтем: аз таснифгарҳо, тарҷумонҳо ва чатботҳо то трансформерҳои биноӣ ва бисёрмодалӣ. Гарчанде трансформерҳо хеле чандиру тавоноанд, онҳо ҳанӯз комил нестанд. Аз ҷумла, метавонанд хеле суст кор кунанд, махсусан ҳангоми коркарди пайдарпайиҳои дарози вурудӣ.

Хушбахтона, барои суръат бахшидан ба трансформерҳои ҳар андоза усулҳои зиёде таҳия шудаанд:

  • Барои тезонидани рамзкушоӣ дар трансформерҳои тавлидӣ, аз кешкунии калидҳо ва қиматҳо (KV caching) ва рамзкушоии тахминӣ (speculative decoding) истифода мебарем. Сипас чанд равиши параллелсозии тавлиди матнро кӯтоҳ баррасӣ мекунем.
  • Барои тезонидани диққати бисёрсарӣ (MHA), ки яке аз ҷузъҳои серхароҷоти ҳисоббарории трансформер аст, диққати камзич (sparse attention), диққати тақрибӣ (approximate attention), истифодаи муштараки проексияҳо ва FlashAttention-ро меомӯзем.
  • Барои суръат бахшидан ба трансформерҳои азими дорои то триллионҳо параметр, омехтаи коршиносон (Mixture of Experts — MoE)-ро баррасӣ мекунем.
  • Барои омӯзиши самараноки трансформерҳои калон, мутобиқсозии камхарҷи параметрӣ (Parameter-Efficient Fine-Tuning — PEFT) бо адаптерҳое чун мутобиқсозии рутбаи паст (Low-Rank Adaptation — LoRA), инчунин нигоҳдории нуқтаҳои назоратии фаъолшавӣ (activation checkpointing), бастабандии пайдарпайиҳо (sequence packing), ҷамъкунии градиентҳо (gradient accumulation) ва параллелсозӣ-ро меомӯзем.

Эзоҳ. Дар манбаи тарҷума аз ин боб танҳо муқаддимаи боло ва нақшаи чорқисма буд. Боқимондаи боб аз рӯйи ҳамин нақша аз нав навишта шуд; он тарҷумаи матни Жерон нест. Ҳамаи кодҳо дар Python 3.12, PyTorch 2.14 ва Transformers 5.20 санҷида шуданд; ададҳои вақт дар Apple M4 Pro (MPS) чен карда шудаанд ва дар GPU-и шумо дигар хоҳанд буд.

Тезонидани рамзкушоӣ дар трансформерҳои тавлидӣ

Модели танҳо декодер матнро токен ба токен тавлид мекунад: ҳар токени нав ба вуруд илова шуда, модел боз як гузариши пурра мекунад. Агар промпт 1000 токен бошад ва 500 токени нав хоҳем, модели содда 500 бор тамоми пайдарпайии то 1500 токенро аз нав коркард мекунад. Аммо дар декодери сабабӣ ҳолатҳои токенҳои пешина аз токенҳои оянда вобаста нестанд — онҳо ҳеҷ гоҳ тағйир намеёбанд. Пас ҳисоб кардани онҳо аз нав исрофи холис аст.

Кешкунии калидҳо ва қиматҳо

Дар ҳар қабати диққат токени нав бояд ба калидҳо ва қиматҳои ҳамаи токенҳои пешина (K ва V) нигарад. Ин калидҳо ва қиматҳо барои токенҳои кӯҳна ҳар қадам айнан ҳамонанд. Ғояи кеши KV содда аст: K ва V-и ҳар қабатро баъди ҳисоб нигоҳ дошта, дар қадами навбатӣ танҳо барои токени нав query, калид ва қимат ҳисоб мекунем ва калиду қиматро ба охири кеш мечаспонем (расми 17.1).

Кеши калидҳо ва қиматҳо

Расми 17.1. Бе кеш (чап) ҳар қадам K ва V-и ҳамаи токенҳо аз нав ҳисоб мешаванд; бо кеш (рост) танҳо K ва V-и токени нав ҳисоб шуда, ба кеш илова мешаванд.

Пас ҳар қадами тавлид ба ҷойи коркарди L токен танҳо як токенро аз шабака мегузаронад. Диққат ҳанӯз ба ҳамаи L калид менигарад, вале ҳамаи қабатҳои зич, ки бештари ҳисобро мегиранд, танҳо бо як вектор кор мекунанд.

Қабати self-attention-и сабабии боби 15-ро бо кеш месозем:

import torch
import torch.nn as nn
import torch.nn.functional as F

class CachedSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super().__init__()
        self.h, self.d = num_heads, embed_dim // num_heads
        self.qkv_proj = nn.Linear(embed_dim, 3 * embed_dim)
        self.out_proj = nn.Linear(embed_dim, embed_dim)

    def forward(self, X, cache=None):
        B, L, _ = X.shape
        q, k, v = self.qkv_proj(X).view(B, L, 3, self.h, self.d).permute(2, 0, 3, 1, 4)
        if cache is not None:
            k = torch.cat([cache[0], k], dim=2)  # калидҳои кӯҳна + нав
            v = torch.cat([cache[1], v], dim=2)
        is_causal = cache is None and L > 1
        Z = F.scaled_dot_product_attention(q, k, v, is_causal=is_causal)
        Z = Z.transpose(1, 2).reshape(B, L, self.h * self.d)
        return self.out_proj(Z), (k, v)

Се проексияи Q, K, V ин ҷо як nn.Linear бо баромади се баробар калонтар мебошанд, мисли GPT-2. Баъди view() ва permute() ҳар яки q, k, v шакли [B, h, L, d] доранд. Агар кеш дода шавад, калидҳо ва қиматҳои нав дар меҳвари вақт (dim=2) ба кӯҳнаҳо часпонида мешаванд. Ниқоби сабабӣ танҳо ҳангоми коркарди промпт лозим аст: токени ягонаи нав ба ҳамаи токенҳои пешин ва худаш нигоҳ карда метавонад, пас ниқоб намехоҳад. Қабат баромад ва кеши навро бармегардонад.

Санҷем, ки натиҷа бо ҳисоби пурра баробар аст: даҳ токенро якбора медиҳем, баъд нӯҳтоашро бо кеш ва даҳуминро алоҳида:

>>> torch.manual_seed(42)
>>> attn = CachedSelfAttention(embed_dim=64, num_heads=4)
>>> X = torch.randn(1, 10, 64)
>>> full, _ = attn(X)
>>> out, cache = attn(X[:, :9])
>>> step, cache = attn(X[:, 9:], cache)
>>> torch.allclose(full[:, 9:], step, atol=1e-6)
True
>>> cache[0].shape
torch.Size([1, 4, 10, 16])

Ҳамон натиҷа, вале қадами охир танҳо як токенро аз проексияҳо гузаронд. Кеш акнун 10 калид барои ҳар яке аз 4 сар дорад.

Китобхонаи Transformers кешро худкор истифода мебарад: generate() пешфарз use_cache=True дорад. Фарқро бо GPT-2 чен мекунем — 500 токени нав бо кеш ва бе он:

from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained("gpt2")
gpt2 = AutoModelForCausalLM.from_pretrained("gpt2", device_map="auto")
prompt = "The quickest way to speed up a transformer is"
inputs = tokenizer(prompt, return_tensors="pt").to(gpt2.device)
for use_cache in (True, False):
    outputs = gpt2.generate(**inputs, max_new_tokens=500, min_new_tokens=500,
                            do_sample=False, use_cache=use_cache,
                            pad_token_id=tokenizer.eos_token_id)

Дар мошини мо тавлид бо кеш 1,99 сония ва бе кеш 12,41 сония гирифт — тақрибан 6 маротиба тезтар, ва токенҳои баромад айнан якхела буданд. Ҳар қадар матн дарозтар, фарқ ҳамон қадар калонтар: бе кеш хароҷоти тавлиди N токен квадратӣ аз N меафзояд.

Кеш ройгон нест — хотира мехоҳад. Андозаи онро муодилаи 17.1 медиҳад.

Муодилаи 17.1. Андозаи кеши KV.

M_KV = 2 × n_layers × n_kv × d_head × L × B × b

  • 2 — барои калидҳо ва қиматҳо.
  • n_layers — шумораи қабатҳо, n_kv — шумораи сарҳои калиду қимат дар ҳар қабат, d_head — андозаи ҳар сар.
  • L — дарозии пайдарпайӣ, B — андозаи баста, b — шумораи байтҳо барои як адад (2 барои float16 ё bfloat16).

Mistral-7B 32 қабат, 8 сари KV ва d_head = 128 дорад. Пас як токен 2 × 32 × 8 × 128 × 2 = 131 072 байт, яъне 128 КБ мегирад. Барои равзанаи заминаи 32 768 токен (Mistral-7B аз версияи v0.2 сар карда) ин 4 ГБ-и иловагӣ барои ҳар пайдарпайӣ аст — тақрибан ба андозаи худи модел дар 4 бит баробар. Агар Mistral барои калидҳо ва қиматҳо ҳам 32 сар медошт, кеш 16 ГБ мешуд; дар бахши «Истифодаи муштараки проексияҳо» мебинем, ки чаро чунин нест.

Маслиҳат. Дар хидматрасонии модел ба корбарони зиёд ҳамин кеш, на вазнҳо, маҳдудкунандаи асосии хотира мешавад. Системаҳое чун vLLM кешро ба саҳифаҳои хурд тақсим мекунанд (PagedAttention, 2023), то хотираи холӣ дар байни дархостҳо гум нашавад, ва барои промптҳои муштарак, масалан промпти системавии якхела, саҳифаҳои кешро такроран истифода мебаранд (prefix caching).

Рамзкушоии тахминӣ

Кеш ҳисобро кам мекунад, вале тавлид ҳанӯз пайдарпай аст: як токен дар як гузариш. Дар GPU-и муосир як гузариши модели калон барои як токен бо суръати хондани вазнҳо аз хотира маҳдуд аст, на бо ҳисоб: барои ҳар токен ҳамаи миллиардҳо вазн хонда мешаванд, вале бо онҳо хеле кам амал иҷро мешавад. Гузариши модел барои 5 токен тақрибан ҳамон қадар вақт мегирад, ки барои як токен.

Рамзкушоии тахминӣ (Leviathan ва ҳамкорон, Google, 2022; Chen ва ҳамкорон, DeepMind, 2023) аз ин истифода мебарад. Модели хурд ва тез — модели пешниҳодкунанда (draft model) — k токенро пайдарпай пешниҳод мекунад. Баъд модели калон — модели ҳадаф — ҳамаи k токенро дар як гузариш месанҷад, чунки барои ҳар мавқеъ пешгӯии худро якбора медиҳад. Токенҳо аз чап ба рост то нахустин номувофиқат қабул мешаванд; дар ҷои номувофиқат токени модели ҳадаф гирифта мешавад (расми 17.2). Пас ҳар гузариши модели ҳадаф на як, балки аз 1 то k + 1 токен медиҳад.

Рамзкушоии тахминӣ

Расми 17.2. Рамзкушоии тахминӣ: модели хурд чор токен пешниҳод мекунад, модели калон ҳамаро дар як гузариш месанҷад, ду токени аввалро қабул карда, сеюмро бо токени худ иваз мекунад.

Муҳимтар аз ҳама: натиҷа айнан ҳамон аст, ки модели ҳадаф худаш медод. Дар рамзкушоии ҳарисона (greedy) ин равшан аст — мо танҳо токенҳоеро мегирем, ки модели ҳадаф ҳам интихоб мекард. Ҳангоми намунагирӣ қоидаи махсуси қабул лозим аст (муодилаи 17.2).

Муодилаи 17.2. Қоидаи қабул дар намунагирии тахминӣ.

P(қабул x) = min(1, p(x) / q(x))

Агар рад шавад: x ~ max(0, p(x) − q(x)) / Σₓ′ max(0, p(x′) − q(x′))

Ин ҷо q(x) эҳтимолияти токени x аз рӯйи модели пешниҳодкунанда ва p(x) аз рӯйи модели ҳадаф аст. Агар модели хурд ба токен камтар аз модели калон эҳтимолият диҳад, токен ҳамеша қабул мешавад; агар бештар диҳад, бо эҳтимолияти p/q. Ҳангоми рад токени нав аз «фарқи» ду тақсимот гирифта мешавад. Метавон исбот кард, ки токенҳои ҳосилшуда айнан аз тақсимоти p меоянд.

Версияи ҳарисонаро аз сифр менависем. Модели ҳадаф GPT-2 (124 миллион параметр) ва пешниҳодкунанда DistilGPT-2 (82 миллион) аст; ҳар ду як токенсоз доранд, ки шарти ҳатмист:

draft = AutoModelForCausalLM.from_pretrained("distilgpt2", device_map="auto")
gpt2.eval()
draft.eval()

@torch.no_grad()
def speculative_generate(target, draft, input_ids, max_new_tokens=60, k=4):
    ids = input_ids
    n_drafted = n_accepted = 0
    while ids.shape[1] - input_ids.shape[1] < max_new_tokens:
        draft_ids = draft.generate(ids, max_new_tokens=k, do_sample=False,
                                   pad_token_id=tokenizer.eos_token_id)
        proposal = draft_ids[:, ids.shape[1]:]                   # [1, k]
        logits = target(draft_ids).logits[:, ids.shape[1] - 1:]  # [1, k+1, V]
        target_tokens = logits.argmax(dim=-1)                    # [1, k+1]
        matches = (proposal == target_tokens[:, :-1]).int().cumprod(dim=1)
        n = matches.sum().item()
        ids = torch.cat([ids, proposal[:, :n], target_tokens[:, n:n + 1]], dim=1)
        n_drafted += proposal.shape[1]
        n_accepted += n
    return ids[:, :input_ids.shape[1] + max_new_tokens], n_accepted / n_drafted

Модели пешниҳодкунанда k токен тавлид мекунад. Модели ҳадаф тамоми пайдарпайиро як бор мегузаронад; логитҳои мавқеъҳои аз охири промпт сар карда k + 1 пешгӯӣ медиҳанд: k-тоаш барои санҷиши токенҳои пешниҳодшуда ва якеаш барои токени баъд аз онҳо. cumprod() мувофиқатҳоро то нахустин номувофиқат нигоҳ медорад, пас n шумораи токенҳои қабулшуда аст. n токени қабулшуда ва як токени модели ҳадаф илова мешаванд. Ҳатто агар ҳама рад шаванд, як токен пеш меравем — на камтар аз тавлиди оддӣ.

>>> input_ids = tokenizer("The history of the Roman Empire",
...                       return_tensors="pt").input_ids.to(gpt2.device)
>>> spec_ids, acceptance = speculative_generate(gpt2, draft, input_ids)
>>> greedy_ids = gpt2.generate(input_ids, max_new_tokens=60, do_sample=False,
...                            pad_token_id=tokenizer.eos_token_id)
>>> round(acceptance, 3), torch.equal(spec_ids, greedy_ids)
(0.553, True)

55% токенҳои пешниҳодшуда қабул шуданд ва натиҷа бо тавлиди ҳарисонаи худи GPT-2 як ба як баробар аст. Барои соддагӣ ин версия кеши KV-ро истифода намебарад ва ҳар давр ҳамаро аз нав ҳисоб мекунад; татбиқҳои ҷиддӣ кешро нигоҳ дошта, дар ҳолати рад қисми охирашро мебуранд.

Transformers ин усулро бо аргументи assistant_model дорад (assisted generation):

outputs = gpt2.generate(**inputs, max_new_tokens=200, do_sample=False,
                        assistant_model=draft,
                        pad_token_id=tokenizer.eos_token_id)

Аммо дар мошини мо бе ёрдамчӣ тавлиди 200 токен 0,66 сония ва бо ёрдамчӣ 2,53 сония гирифт — суст шуд! Сабаб равшан аст: DistilGPT-2 ҳамагӣ якуним маротиба аз GPT-2 хурдтар аст ва ҳар ду он қадар хурданд, ки вақтро на хондани вазнҳо аз хотира, балки хароҷоти идоракунӣ (оғози ядроҳо, кори Python) мегирад; пас санҷиши токенҳо бо модели ҳадаф қариб ҳеҷ чизро сарфа намекунад. Рамзкушоии тахминӣ вақте самар медиҳад, ки модели ҳадаф аз пешниҳодкунанда даҳҳо маротиба калонтар бошад, масалан модели 70-миллиардӣ ва ёрдамчии 1-миллиардии ҳамон оила. Дар чунин ҳолатҳо суръат одатан 2–3 маротиба меафзояд.

Маслиҳат. Модели ёрдамчӣ ҳамеша лозим нест. Ҷустуҷӯи промпт (prompt lookup decoding) токенҳои пешниҳодиро аз худи промпт мегирад: агар охирин чанд токен дар промпт вохӯрда бошанд, токенҳои баъд аз онҳо пешниҳод мешаванд. Барои вазифаҳое, ки ҷавоб қисмҳои вурудро такрор мекунад — хулосаи ҳуҷҷат, таҳрири код, RAG — ин хеле самаранок аст. Дар Transformers: generate(..., prompt_lookup_num_tokens=10).

Тавлиди параллелӣ

Чанд равиши дигар ҳам тавлидро аз пайдарпаии қатъӣ озод мекунанд:

  • Чанд сари пешгӯӣ. Medusa (2024) ба модел чанд сари иловагӣ медиҳад, ки токенҳои +2, +3, …-ро пешгӯӣ мекунанд; онҳо мисли модели пешниҳодкунанда кор мекунанд, вале модели дуюм лозим нест. Пешгӯии бисёртокена (multi-token prediction, Meta, 2024) моделро аз оғоз барои пешгӯии якчанд токени оянда меомӯзонад; DeepSeek-V3 ҳамин тавр омӯхта шудааст.
  • Рамзкушоии пешнигоҳ (lookahead decoding, 2023) пешниҳодҳоро бо итератсияҳои Якобӣ дар худи модел месозад.
  • Моделҳои забонии диффузионӣ тамоми ҷавобро якбора тавлид карда, тадриҷан аниқ мекунанд — монанди моделҳои диффузионии тасвир дар боби 18.
  • Бастабандии пайваста (continuous batching, Orca, 2022) дар сатҳи сервер кор мекунад: дархостҳои гуногунро дар як баста ҷамъ мекунад ва ба ҷойи интизории анҷоми тамоми баста ҳар дархости тамомшударо фавран бо дархости нав иваз менамояд. Ин суръати модел барои як корбарро тағйир намедиҳад, вале шумораи токенҳо дар сонияро барои ҳамаи корбарон чанд маротиба зиёд мекунад.

Тезонидани диққати бисёрсарӣ

Кеши KV тавлидро тез мекунад, вале худи диққат ҳанӯз барои пайдарпайии L токен матритсаи холҳои L × L месозад. Барои 128 000 токен ин 16 миллиард хол дар ҳар сар ва ҳар қабат аст. Ҳоло чор оилаи роҳҳои ҳалро мебинем.

Диққати камзич

Оё ҳар токен воқеан бояд ба ҳамаи токенҳо нигарад? Аксар вақт вазнҳои диққат дар чанд мавқеъ ҷамъ мешаванд ва дигарон қариб сифранд. Диққати камзич ба ҳар токен иҷозат медиҳад, ки танҳо ба зермаҷмӯи токенҳо нигарад; ниқоби диққат он қисмро муайян мекунад (расми 17.3):

  • Диққати равзанаи лағжанда (sliding window attention): ҳар токен танҳо ба w токени охирин менигарад. Хароҷот аз O(L²) ба O(L·w) мефарояд. Бо якчанд қабат майдони дид васеъ мешавад: дар қабати дуюм токен ба токенҳое менигарад, ки худ ба w токени пеш нигоҳ кардаанд, пас дар n қабат то n·w токенро фаро мегирад — мисли майдони қабули CNN-ҳо дар боби 12. Версияи аввали Mistral-7B (v0.1) равзанаи 4096-токена дошт.
  • Токенҳои умумӣ (global tokens): чанд токени махсус, масалан CLS, ба ҳама менигаранд ва ҳама ба онҳо менигаранд. Longformer (Allen AI, 2020) равзанаи лағжанда ва токенҳои умумиро омехта кард; BigBird (Google, 2020) боз диққати тасодуфиро илова намуд.
  • Нақшҳои қадамдор (strided): Sparse Transformer (OpenAI, 2019) ба токенҳои наздик ва ба ҳар токени k-уми пешина менигарист.

Ниқобҳои диққати камзич

Расми 17.3. Ниқобҳои диққат барои 16 токен: сабабии пурра (чап), равзанаи лағжанда бо w = 4 (миёна) ва равзанаи лағжанда бо токени умумии 0, ки ҳамаи токенҳо ба он менигаранд (рост). Ҳуҷайраҳои ранга ҷуфтҳоеанд, ки диққат ба онҳо иҷозат дорад.

Сохтани ниқоби равзанаи лағжанда осон аст:

def sliding_window_causal_mask(L, window):
    i = torch.arange(L).unsqueeze(1)  # мавқеи query
    j = torch.arange(L).unsqueeze(0)  # мавқеи key
    return (j  i - window)  # True = диққат иҷозат дорад
>>> sliding_window_causal_mask(8, 3).int()
tensor([[1, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0, 0, 0, 0],
        [0, 0, 1, 1, 1, 0, 0, 0],
        [0, 0, 0, 1, 1, 1, 0, 0],
        [0, 0, 0, 0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0, 1, 1, 1]], dtype=torch.int32)

Огоҳӣ. Ин ниқоб ба F.scaled_dot_product_attention(q, k, v, attn_mask=mask) дода мешавад. Дар ин функсия маънои ниқоби мантиқӣ баръакси nn.MultiheadAttention аст: True яъне «диққат иҷозат дорад». Ғайр аз ин, ниқобро ба ин тарз гузоштан хотираро сарфа намекунад — матритсаи пурраи L × L боз ҳисоб мешавад. Сарфаи воқеӣ танҳо аз ядроҳои махсус меояд, ки қисмҳои ниқобшударо умуман ҳисоб намекунанд, масалан FlexAttention-и PyTorch (torch.nn.attention.flex_attention), ки ниқобро ҳамчун функсия мегирад ва бо torch.compile ядрои камзич месозад.

Барои кеш ҳам фоида дорад: бо равзанаи w кеш танҳо w токени охиринро нигоҳ медорад (кеши даврӣ, rolling buffer cache), пас хотира аз дарозии матн вобаста намешавад.

Диққати тақрибӣ

Роҳи дигар — ҳисоб накардани матритсаи L × L умуман, балки тақрибан ҳисоб кардани натиҷаи он:

  • Linformer (Facebook AI, 2020) калидҳо ва қиматҳоро дар меҳвари пайдарпайӣ бо проексияи омӯхташаванда аз L ба k мавқеъ (масалан k = 256) фишурда мекунад. Матритсаи холҳо L × k мешавад ва хароҷот хаттӣ аз L. Ғоя ба мушоҳидае асос меёбад, ки матритсаи диққат одатан рутбаи паст дорад (боби 7-ро дар бораи SVD ба ёд оред).
  • Reformer (Google, 2020) бо хешкунии ҳассос ба маҳал (locality-sensitive hashing, LSH) калидҳоеро, ки ба query наздиканд, ба як сабад меандозад ва диққатро танҳо дар дохили сабадҳо ҳисоб мекунад.
  • Диққати хаттӣ (linear attention; Katharopoulos ва ҳамкорон, 2020) softmax-ро бо зарби скалярии хусусиятҳо φ(q)ᵀφ(k) иваз мекунад. Он гоҳ тартиби зарбро иваз кардан мумкин аст (муодилаи 17.3).

Муодилаи 17.3. Диққати хаттӣ.

zᵢ = Σⱼ φ(qᵢ)ᵀφ(kⱼ) vⱼ / Σⱼ φ(qᵢ)ᵀφ(kⱼ) = φ(qᵢ)ᵀ (Σⱼ φ(kⱼ) vⱼᵀ) / (φ(qᵢ)ᵀ Σⱼ φ(kⱼ))

Ҷамъи дар қавс як матритсаи d × d аст, ки аз i вобаста нест: як бор ҳисоб шуда, барои ҳамаи query-ҳо истифода мешавад. Хароҷот O(L·d²) мешавад, на O(L²·d). Дар ҳолати сабабӣ ҷамъ танҳо то i меравад ва метавон онро қадам ба қадам навсозӣ кард — модел ба RNN бо ҳолати d × d табдил меёбад. Мақола номи «Трансформерҳо RNN-анд» дошт. Performer (Google, 2020) φ-ро тавре интихоб кард, ки ба softmax тақрибан баробар шавад. Дар PyTorch бо φ(x) = ELU(x) + 1:

def linear_attention(Q, K, V):  # Q, K, V: [B, h, L, d]
    Q, K = F.elu(Q) + 1, F.elu(K) + 1          # φ мусбат
    KV = K.transpose(-2, -1) @ V               # [B, h, d, d]
    Z = Q @ KV                                 # [B, h, L, d]
    norm = Q @ K.sum(dim=-2, keepdim=True).transpose(-2, -1)  # [B, h, L, 1]
    return Z / norm

Ҳеҷ матритсаи L × L сохта намешавад. Аммо сифат одатан аз диққати softmax пасттар аст, хусусан дар вазифаҳое, ки ёфтани токени дақиқ дар матни дарозро талаб мекунанд. Барои ҳамин моделҳои бузурги забонии маъмул ҳанӯз диққати пурраро истифода мебаранд, ё қабатҳои хаттиро бо чанд қабати диққати пурра омехта мекунанд. Ғояи ҳолати собити қадам ба қадам ба моделҳои фазои ҳолат (state space models), монанди Mamba (2023), низ оварда расонд, ки дар матнҳои дароз бо трансформерҳо рақобат мекунанд.

Истифодаи муштараки проексияҳо

Дар бахши кеши KV дидем, ки хотира ба шумораи сарҳои калиду қимат мутаносиб аст. Ҳангоми тавлид маҳз хондани ин кеш аз хотира вақт мегирад. Ноам Шазир соли 2019 пешниҳод кард, ки ҳамаи сарҳои query як калид ва як қиматро муштарак истифода баранд: диққати бисёрдархостӣ (multi-query attention, MQA). Кеш h маротиба хурд мешавад, вале сифат каме паст мефарояд.

Диққати гурӯҳии дархостҳо (grouped-query attention, GQA; Google, 2023) миёнаравӣ аст: h сари query ба g гурӯҳ тақсим мешаванд ва ҳар гурӯҳ як сари калиду қимат дорад (расми 17.4). MHA ҳолати g = h ва MQA ҳолати g = 1 аст. Mistral-7B 32 сари query ва 8 сари KV дорад — барои ҳамин кеши он 4 ГБ буд, на 16. Llama 2 70B ва қариб ҳамаи моделҳои кушодаи нав GQA-ро истифода мебаранд.

MHA, GQA ва MQA

Расми 17.4. Диққати бисёрсарӣ (чап): ҳар сари query калиду қимати худро дорад. GQA (миёна): ҳар ду сари query як ҷуфти калиду қиматро муштарак истифода мебаранд. MQA (рост): ҳамаи сарҳо як ҷуфт доранд.

Дар код GQA танҳо такрори калидҳо ва қиматҳо барои ҳар гурӯҳ аст. F.scaled_dot_product_attention инро бо enable_gqa=True худаш мекунад:

>>> B, L, h, h_kv, d = 2, 16, 8, 2, 32
>>> q = torch.randn(B, h, L, d)
>>> k = torch.randn(B, h_kv, L, d)
>>> v = torch.randn(B, h_kv, L, d)
>>> z1 = F.scaled_dot_product_attention(
...     q, k.repeat_interleave(h // h_kv, dim=1),
...     v.repeat_interleave(h // h_kv, dim=1), is_causal=True)
>>> z2 = F.scaled_dot_product_attention(q, k, v, is_causal=True, enable_gqa=True)
>>> z1.shape, torch.allclose(z1, z2, atol=1e-6)
(torch.Size([2, 8, 16, 32]), True)

repeat_interleave() ҳар сари KV-ро чор бор такрор мекунад, то сарҳои query 0–3 сари KV 0 ва сарҳои 4–7 сари KV 1-ро бинанд. Кеш танҳо 2 сарро нигоҳ медорад; такрор танҳо ҳангоми ҳисоб рӯй медиҳад.

Эзоҳ. DeepSeek-V2 (2024) як қадам пештар рафт: диққати пинҳонии бисёрсарӣ (multi-head latent attention, MLA) калидҳо ва қиматҳоро ба як вектори пинҳонии хурд фишурда, ҳамон векторро кеш мекунад ва калиду қиматҳои ҳар сарро аз он ҳангоми ҳисоб барқарор менамояд. Кеш аз GQA ҳам хурдтар мешавад, вале сифат ба MHA наздик мемонад.

FlashAttention

Ҳамаи усулҳои боло ҳисобро тағйир медоданд. FlashAttention (Три Дао ва ҳамкорон, Стэнфорд, 2022) ҳамон диққати пурра ва дақиқ-ро ҳисоб мекунад, вале чанд маротиба тезтар. Сирр дар он аст, ки дар GPU-и муосир маҳдудкунанда на шумораи амалҳо, балки интиқоли маълумот байни хотираи калон ва суст (HBM, даҳҳо ГБ) ва хотираи хурд ва хеле тези дохили чип (SRAM, чанд даҳ МБ) аст. Татбиқи одӣ матритсаи L × L-и холҳоро ба HBM менависад, барои softmax бармехонад, натиҷаро боз менависад ва боз барои зарб ба V мехонад.

FlashAttention Q, K ва V-ро ба блокҳо (tiles) тақсим мекунад, ки ба SRAM мегунҷанд, ва барои ҳар блоки query аз блокҳои K ва V мегузарад, бе он ки матритсаи пурраи холҳоро ягон бор ба HBM нависад. Мушкил softmax аст: барои муқаррарсозӣ ҷамъи ҳамаи экспонентаҳо лозим аст, ки то охир маълум нест. Ҳалли он softmax-и онлайн (Milakov ва Gimelshein, 2018) мебошад: максимуми ҷорӣ m ва ҷамъи ҷорӣ ℓ нигоҳ дошта мешаванд ва ҳар блоки нав онҳоро ислоҳ мекунад (муодилаи 17.4).

Муодилаи 17.4. Навсозии softmax-и онлайн барои блоки холҳои s ва қиматҳои V.

m′ = max(m, maxⱼ sⱼ)

ℓ′ = e^(m − m′) · ℓ + Σⱼ e^(sⱼ − m′)

o′ = e^(m − m′) · o + Σⱼ e^(sⱼ − m′) · vⱼ

Дар охир натиҷа o / ℓ аст. Агар блоки нав максимуми калонтар дошта бошад, ҳама чизи ҷамъшуда бо омили e^(m−m′) кам карда мешавад. Барои як query дар PyTorch-и одӣ (ин ядрои GPU нест, балки тасвири ҳисоб):

import math

def online_softmax_attention(q, K, V, block=4):
    m = torch.tensor(-torch.inf)
    s = torch.tensor(0.0)
    acc = torch.zeros(V.shape[-1])
    for start in range(0, K.shape[0], block):
        scores = K[start:start + block] @ q / math.sqrt(q.shape[0])
        m_new = torch.maximum(m, scores.max())
        scale = torch.exp(m - m_new)
        p = torch.exp(scores - m_new)
        s = s * scale + p.sum()
        acc = acc * scale + p @ V[start:start + block]
        m = m_new
    return acc / s
>>> torch.manual_seed(0)
>>> q, K, V = torch.randn(64), torch.randn(100, 64), torch.randn(100, 32)
>>> reference = torch.softmax(K @ q / 8, dim=0) @ V
>>> torch.allclose(online_softmax_attention(q, K, V), reference, atol=1e-5)
True

Натиҷа бо softmax-и муқаррарӣ баробар аст, вале ҳеҷ гоҳ ҳамаи 100 холро якбора нигоҳ надоштем. Ҳангоми паҳнкунии баръакс FlashAttention матритсаи диққатро нигоҳ намедорад, балки аз нав ҳисоб мекунад — арзонтар аз хондани он аз HBM. Хотира аз O(L²) ба O(L) мефарояд ва суръат дар GPU-ҳои NVIDIA 2–4 маротиба меафзояд. FlashAttention-2 (2023) ва FlashAttention-3 (2024, барои H100) кори ядроро боз беҳтар тақсим карданд.

Хушбахтона, худатон ядро навиштан лозим нест. F.scaled_dot_product_attention(), ки аз боби 15 истифода мебарем, дар CUDA худкор ядрои FlashAttention ё ядрои ба он монанди сарфакунандаи хотираро интихоб мекунад, агар вурудҳо имкон диҳанд (масалан float16/bfloat16 ва бе ниқоби дилхоҳ). Дар Transformers метавон татбиқро ошкоро интихоб кард:

model = AutoModelForCausalLM.from_pretrained(
    model_id, dtype=torch.bfloat16, attn_implementation="sdpa")

Қиматҳои дигар "eager" (татбиқи одии PyTorch) ва "flash_attention_2" (бастаи flash-attn, танҳо барои GPU-ҳои NVIDIA) мебошанд.

Омехтаи коршиносон

Сифати модели забонӣ бо шумораи параметрҳо меафзояд, вале хароҷоти ҳар токен ҳам. Омехтаи коршиносон (Mixture of Experts, MoE) ин ду чизро аз ҳам ҷудо мекунад: модел параметрҳои зиёд дорад, вале ҳар токен танҳо қисми хурди онҳоро истифода мебарад.

Дар трансформери MoE қабати пешрави (FFN) ҳар блок бо E шабакаи пешрави мустақил — коршиносон — иваз мешавад. Масиргузор (router), ки як қабати хаттии хурд аст, барои ҳар токен холи ҳар коршиносро ҳисоб мекунад; танҳо k коршиноси беҳтарин (одатан 1 ё 2) иҷро мешаванд ва баромадҳояшон бо вазнҳои масиргузор ҷамъ мешаванд (расми 17.5, муодилаи 17.5).

Қабати омехтаи коршиносон

Расми 17.5. Қабати MoE бо 8 коршинос ва k = 2: масиргузор барои ҳар токен ду коршиносро интихоб мекунад ва баромадҳои онҳоро бо вазнҳо ҷамъ мекунад.

Муодилаи 17.5. Баромади қабати MoE.

y = Σ_(i ∈ 𝒯(x)) gᵢ(x) · Eᵢ(x)

gᵢ(x) = exp(wᵢᵀx) / Σ_(j ∈ 𝒯(x)) exp(wⱼᵀx)

  • 𝒯(x) — маҷмӯи k коршинос бо холҳои баландтарин wᵢᵀx.
  • Eᵢ(x) — баромади коршиноси i, gᵢ(x) — вазни он; softmax танҳо дар байни k коршиноси интихобшуда гирифта мешавад.

Mixtral 8x7B (Mistral AI, 2023) 8 коршинос ва k = 2 дошт: 46,7 миллиард параметри умумӣ, вале ҳар токен танҳо 12,9 миллиардро истифода мебарад. Пас бо хароҷоти модели 13-миллиардӣ дар аксари санҷишҳо ба Llama 2 70B баробар ё аз он беҳтар буд. DeepSeek-V3 (2024) 671 миллиард параметр дорад, ки аз онҳо 37 миллиард барои ҳар токен фаъоланд. Аксари моделҳои калонтарини ҳозира MoE мебошанд.

Татбиқи содда:

class MoE(nn.Module):
    def __init__(self, embed_dim, ff_dim, n_experts=8, top_k=2):
        super().__init__()
        self.top_k = top_k
        self.router = nn.Linear(embed_dim, n_experts, bias=False)
        self.experts = nn.ModuleList([
            nn.Sequential(nn.Linear(embed_dim, ff_dim), nn.GELU(),
                          nn.Linear(ff_dim, embed_dim))
            for _ in range(n_experts)])

    def forward(self, X):
        B, L, D = X.shape
        X = X.reshape(-1, D)                          # [N, D], N = B × L
        weights, idx = self.router(X).topk(self.top_k, dim=-1)  # [N, k]
        weights = weights.softmax(dim=-1)
        out = torch.zeros_like(X)
        for e, expert in enumerate(self.experts):
            token_ids, slot = (idx == e).nonzero(as_tuple=True)
            if token_ids.numel() > 0:
                out[token_ids] += weights[token_ids, slot, None] * expert(X[token_ids])
        self.load = torch.bincount(idx.flatten(), minlength=len(self.experts))
        return out.reshape(B, L, D)

Токенҳо ба як рӯйхат ҳамвор мешаванд. Масиргузор барои ҳар токен k коршинос ва холҳояшонро медиҳад; softmax онҳоро ба вазнҳо табдил медиҳад. Баъд ҳар коршинос танҳо токенҳоеро мегирад, ки ба он фиристода шудаанд: nonzero() рақами токен ва ҷояшро дар k-и он медиҳад. self.load шумораи токенҳои ҳар коршиносро нигоҳ медорад:

>>> torch.manual_seed(42)
>>> moe = MoE(embed_dim=128, ff_dim=512)
>>> Y = moe(torch.randn(4, 32, 128))
>>> Y.shape, moe.load.tolist()
(torch.Size([4, 32, 128]), [33, 31, 22, 31, 39, 30, 29, 41])
>>> sum(p.numel() for p in moe.parameters())
1054720

Қабат 1 054 720 параметр дорад, вале ҳар токен танҳо ду коршиноси 131 712-параметрӣ ва масиргузорро истифода мебарад — тақрибан чоряки онро.

Мушкили асосии MoE номутавозинии сарборӣ аст. Агар масиргузор дар оғоз якчанд коршиносро афзал донад, онҳо беҳтар меомӯзанд, боз бештар интихоб мешаванд ва дигарон бекор мемонанд. Ҳангоми омӯзиш дар чанд GPU коршиноси серсарбор ҳамаро интизор мегузорад ва баъзе токенҳо партофта мешаванд, агар коршинос аз ҳад пур бошад. Switch Transformer (Google, 2021) ба талафот талафоти мувозинати сарборӣ-ро илова кард (муодилаи 17.6).

Муодилаи 17.6. Талафоти иловагии мувозинати сарборӣ.

ℒ_aux = α · E · Σᵢ₌₁ᴱ fᵢ · Pᵢ

Ин ҷо fᵢ ҳиссаи токенҳоест, ки ба коршиноси i фиристода шуданд, Pᵢ миёнаи эҳтимолияти масиргузор барои коршиноси i дар баста ва α омили хурд (масалан 0,01). Талафот вақте хурдтарин аст, ки ҳарду тақсимот баробар бошанд. DeepSeek-V3 қариб бе ин талафот кор мекунад: ҳангоми интихоби коршиносон ба холи ҳар яке ислоҳи динамикӣ (bias) илова мешавад, ки пас аз ҳар қадам барои коршиносони серсарбор кам ва барои камсарборҳо зиёд карда мешавад (талафоти иловагии хеле хурд танҳо ҳамчун эҳтиёт боқӣ мемонад).

Огоҳӣ. MoE ҳисобро кам мекунад, на хотираро: ҳамаи коршиносон бояд дар хотира бошанд, чунки маълум нест, ки токени оянда ба кадомаш меравад. Mixtral 8x7B дар float16 тақрибан 94 ГБ мехоҳад. MoE барои серверҳои дорои чанд GPU, ки ҳар коршинос метавонад дар GPU-и худ бошад (параллелсозии коршиносон), беҳтарин аст, на барои як GPU-и хонагӣ.

Омӯзиши самараноки трансформерҳои калон

То ин ҷо тезонидани истифодаи модел, яъне хулосабарорӣ (inference)-ро дидем. Омӯзиш боз ҳам гаронтар аст: барои ҳар параметр ғайр аз вазн градиент ва ду ҳолати Adam ҳам нигоҳ дошта мешаванд, илова бар ин ҳамаи фаъолшавиҳо барои паҳнкунии баръакс. Барои омӯзиши пурраи модели 7-миллиардӣ бо Adam ва дақиқии омехта тақрибан 16 байт барои ҳар параметр, яъне 112 ГБ, танҳо барои вазнҳо ва ҳолатҳои оптимизатор лозим аст — бе фаъолшавиҳо.

Мутобиқсозии камхарҷи параметрӣ ва LoRA

Барои мутобиқ кардани модели пешомӯхта ба вазифаи нав (fine-tuning) шояд ҳамаи параметрҳоро тағйир додан лозим набошад. PEFT вазнҳои аслиро ях мекунад ва танҳо шумораи хурди параметрҳои навро меомӯзонад. Усулҳои аввал адаптерҳо (Houlsby ва ҳамкорон, 2019) буданд — қабатҳои хурди иловагӣ дар дохили ҳар блок — ва танзими префикс ё танзими промпт (2021), ки якчанд вектори омӯхташавандаро ба оғози пайдарпайӣ илова мекунанд. Аммо маъмултарин имрӯз LoRA (Ҳу ва ҳамкорон, Microsoft, 2021) аст.

Ғояи LoRA: тағйироти ΔW-и вазнҳо ҳангоми fine-tuning рутбаи паст дорад. Пас ба ҷойи омӯзонидани ΔW-и пурраи d × k онро ҳамчун зарби ду матритсаи борик B (d × r) ва A (r × k) бо r хеле хурд (масалан 8) меомӯзонем (расми 17.6, муодилаи 17.7).

LoRA

Расми 17.6. LoRA: вазнҳои пешомӯхтаи W₀ ях карда шудаанд (кабуд); танҳо матритсаҳои хурди A ва B омӯхта мешаванд (норинҷӣ). Баромади онҳо ба баромади W₀ ҷамъ мешавад.

Муодилаи 17.7. Қабати хаттӣ бо LoRA.

h = W₀x + (α / r) · BAx

  • W₀ — вазнҳои пешомӯхта, ки тағйир намеёбанд.
  • A бо қиматҳои хурди тасодуфӣ ва B бо сифрҳо оғоз мешаванд, пас дар оғоз BA = 0 ва модел айнан модели пешомӯхта аст.
  • α/r — омили миқёс; бо он суръати омӯзишро ҳангоми иваз кардани r аз нав танзим кардан лозим намешавад.

Барои қабати 4096 × 4096 матритсаи пурра 16,8 миллион параметр дорад, LoRA бо r = 8 танҳо 8 × (4096 + 4096) = 65 536 — 256 маротиба камтар:

class LoRALinear(nn.Module):
    def __init__(self, base, r=8, alpha=16):
        super().__init__()
        self.base = base
        self.base.requires_grad_(False)  # вазнҳои аслӣ ях мешаванд
        self.A = nn.Parameter(torch.randn(r, base.in_features) / r)
        self.B = nn.Parameter(torch.zeros(base.out_features, r))
        self.scaling = alpha / r

    def forward(self, X):
        return self.base(X) + (X @ self.A.T @ self.B.T) * self.scaling
>>> base = nn.Linear(4096, 4096)
>>> lora = LoRALinear(base, r=8)
>>> sum(p.numel() for p in lora.parameters() if p.requires_grad)
65536
>>> x = torch.randn(3, 4096)
>>> torch.allclose(lora(x), base(x))
True

Тартиби зарб муҳим аст: X @ A.T @ B.T аввал ба r андоза мефарояд, пас матритсаи пурраи d × k ҳеҷ гоҳ сохта намешавад.

LoRA чанд бартарии дигар ҳам дорад. Градиентҳо ва ҳолатҳои Adam танҳо барои A ва B лозиманд, пас хотираи омӯзиш якчанд маротиба кам мешавад: дар мақола барои GPT-3 175B шумораи параметрҳои омӯхташаванда 10 000 маротиба ва хотираи GPU 3 маротиба кам шуд. Адаптери тайёр ҳамагӣ чанд МБ аст: метавон як модели асосӣ ва даҳҳо адаптери гуногунро нигоҳ дошт ва байни онҳо иваз кард. Баъди омӯзиш BA-ро ба W₀ ҷамъ кардан мумкин аст, пас ҳангоми хулосабарорӣ ягон хароҷоти иловагӣ нест.

Китобхонаи PEFT-и Hugging Face ин ҳамаро барои ҳар модели Transformers иҷро мекунад:

from peft import LoraConfig, get_peft_model

gpt2 = AutoModelForCausalLM.from_pretrained("gpt2")
config = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.05,
                    target_modules=["c_attn"], task_type="CAUSAL_LM")
peft_model = get_peft_model(gpt2, config)

target_modules номи қабатҳоеро медиҳад, ки LoRA мегиранд; дар GPT-2 c_attn проексияи якҷояи Q, K, V дар ҳар блок аст. Барои моделҳои дигар номҳо фарқ мекунанд (масалан q_proj, v_proj дар Llama ва Mistral); онҳоро бо print(model) дидан мумкин аст.

>>> peft_model.print_trainable_parameters()
trainable params: 294,912 || all params: 124,734,720 || trainable%: 0.2364

Камтар аз 0,24% параметрҳо омӯхта мешаванд. peft_model мисли модели одӣ ба Trainer ё ба SFTTrainer-и TRL (боби 15) дода мешавад. Баъди омӯзиш peft_model.save_pretrained() танҳо адаптерро нигоҳ медорад ва peft_model.merge_and_unload() вазнҳоро якҷо карда, модели одии GPT2LMHeadModel-ро бармегардонад.

Маслиҳат. QLoRA (Деттмерс ва ҳамкорон, 2023) модели асосиро дар 4 бит (навъи NF4) нигоҳ дошта, адаптерҳои LoRA-ро дар дақиқии баланд меомӯзонад. Бо он модели 65-миллиардиро дар як GPU-и 48-гигабайта fine-tune кардан мумкин шуд. Дар PEFT ин бо боркунии модел бо BitsAndBytesConfig(load_in_4bit=True) (бастаи bitsandbytes, танҳо CUDA) ва баъд get_peft_model() иҷро мешавад.

Нигоҳдории нуқтаҳои назоратии фаъолшавӣ

Ҳангоми гузариши пешравӣ ҳар қабат фаъолшавиҳои худро барои паҳнкунии баръакс нигоҳ медорад. Барои моделҳои амиқ ва пайдарпайиҳои дароз онҳо аз вазнҳо зиёдтар хотира мегиранд. Нигоҳдории нуқтаҳои назоратӣ (activation checkpointing ё gradient checkpointing; Chen ва ҳамкорон, 2016) танҳо вурудҳои баъзе блокҳоро нигоҳ медорад ва фаъолшавиҳои дохили онҳоро ҳангоми паҳнкунии баръакс аз нав ҳисоб мекунад. Мо хотираро бо ҳисоб иваз мекунем: гузариши пешравӣ тақрибан ду бор иҷро мешавад.

Дар PyTorch torch.utils.checkpoint.checkpoint() блокро печонида, фаъолшавиҳои дохилиашро нигоҳ намедорад:

from torch.utils.checkpoint import checkpoint

class Block(nn.Module):
    def __init__(self, d):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(), nn.Linear(4 * d, d))

    def forward(self, X):
        return X + self.net(X)

class DeepModel(nn.Module):
    def __init__(self, d=512, n_blocks=24, use_checkpointing=False):
        super().__init__()
        self.blocks = nn.ModuleList(Block(d) for _ in range(n_blocks))
        self.use_checkpointing = use_checkpointing

    def forward(self, X):
        for block in self.blocks:
            if self.use_checkpointing and self.training:
                X = checkpoint(block, X, use_reentrant=False)
            else:
                X = block(X)
        return X

Барои бастаи 64 пайдарпайии 256-токена фаъолшавиҳои ин модели 24-блока бе нуқтаҳои назоратӣ 6912 МБ ва бо онҳо 768 МБ гирифтанд — 9 маротиба камтар. Ҳар қадами омӯзиш аз 1,09 то 1,30 сония суст шуд, яъне тақрибан 20%. Ин мубодила қариб ҳамеша фоидабахш аст: хотираи озодшуда имкон медиҳад, ки бастаи калонтар ё модели калонтар гирифта шавад.

Дар Transformers ин як сатр аст:

model.gradient_checkpointing_enable()

ё gradient_checkpointing=True дар TrainingArguments.

Бастабандии пайдарпайиҳо

Матнҳои маҷмӯи омӯзишӣ дарозиҳои гуногун доранд. Агар онҳоро то дарозии баландтарин бо pad пур кунем, қисми калони ҳисоб барои токенҳои pad сарф мешавад: дар маҷмӯаҳои гуфтугӯӣ, ки аксари намунаҳо кӯтоҳанд, гоҳо зиёда аз нисфи токенҳо pad мебошанд. Бастабандӣ (packing) якчанд пайдарпайии кӯтоҳро бо токени EoS дар байнашон ба як пайдарпайии дарозии собит мечаспонад (расми 17.7).

Пуркунӣ бо pad ва бастабандӣ

Расми 17.7. Чор ҳуҷҷати дарозиҳои гуногун: бо пуркунӣ бо pad (боло) бисёр ҷойҳо холӣ мемонанд, бо бастабандӣ (поён) ҳуҷҷатҳо дар ду сатри қариб пур ҷой мегиранд.

Функсияи соддаи бастабандӣ, ки барои ҳар ҳуҷҷат мавқеъҳоро аз нав аз 0 оғоз мекунад:

def pack(sequences, max_len, eos_id):
    input_ids, position_ids, buf, pos = [], [], [], []
    for seq in sequences:
        seq = seq + [eos_id]
        if len(buf) + len(seq) > max_len:
            input_ids.append(buf)
            position_ids.append(pos)
            buf, pos = [], []
        buf += seq
        pos += list(range(len(seq)))
    if buf:
        input_ids.append(buf)
        position_ids.append(pos)
    return input_ids, position_ids
>>> pack([[5, 6, 7], [8, 9], [10, 11, 12, 13], [14]], max_len=8, eos_id=0)
([[5, 6, 7, 0, 8, 9, 0], [10, 11, 12, 13, 0, 14, 0]],
 [[0, 1, 2, 3, 0, 1, 2], [0, 1, 2, 3, 4, 0, 1]])

Огоҳӣ. Бе чораҳои иловагӣ токенҳои ҳуҷҷати дуюм ба ҳуҷҷати аввал диққат медиҳанд, ки ба он ҳеҷ иртибот надорад. Барои пешомӯзиш ин одатан қобили қабул аст, вале барои fine-tuning беҳтар аст, ки диққат дар ҳудуди ҳуҷҷат маҳдуд шавад: ниқоби сабабии блокӣ-диагоналӣ ё ядроҳое, ки бо position_ids-и аз нав оғозшаванда сарҳадҳоро мефаҳманд. SFTTrainer-и TRL бо SFTConfig(packing=True) бастабандиро худаш мекунад.

Ҷамъкунии градиентҳо

Моделҳои калон бо бастаҳои калон беҳтар меомӯзанд, вале баста бояд ба хотираи GPU ғунҷад. Ҷамъкунии градиентҳо бастаи калонро ба якчанд микробаста тақсим мекунад: барои ҳар микробаста гузариши пешравӣ ва паҳнкунии баръакс иҷро мешаванд, вале градиентҳо дар .grad ҷамъ шуда, қадами оптимизатор танҳо баъди ҳамаи микробастаҳо гузошта мешавад. Натиҷа ба омӯзиш бо бастаи калон баробар аст (ба истиснои BatchNorm, ки трансформерҳо надоранд):

accumulation_steps = 8
optimizer.zero_grad()
for step, (X_batch, y_batch) in enumerate(train_loader):
    y_pred = model(X_batch)
    loss = loss_fn(y_pred, y_batch) / accumulation_steps
    loss.backward()  # градиентҳо ба .grad ҷамъ мешаванд
    if (step + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

Талафот ба accumulation_steps тақсим мешавад, то ҷамъи градиентҳо ба миёнаи онҳо дар бастаи калон баробар бошад. Агар микробаста 4 бошад, бастаи самаранок 32 аст. Вақт кам намешавад — гузаришҳо ҳамон қадаранд — вале хотира ба андозаи микробаста вобаста мешавад. Дар Transformers: TrainingArguments(per_device_train_batch_size=4, gradient_accumulation_steps=8).

Параллелсозӣ

Ниҳоят, вақте модел ё маълумот ба як GPU намегунҷад, онҳоро байни чанд дастгоҳ тақсим мекунем:

  • Параллелсозии маълумот (data parallelism). Ҳар GPU нусхаи пурраи модел дорад ва қисми худи бастаро коркард мекунад; баъди паҳнкунии баръакс градиентҳо байни ҳамаи GPU-ҳо миёна карда мешаванд (all-reduce). Дар PyTorch: DistributedDataParallel (DDP). Содда ва самаранок, вале модел бояд ба як GPU ғунҷад.
  • Тақсими пурраи ҳолатҳо (fully sharded data parallelism). ZeRO (Microsoft DeepSpeed, 2019) ва FSDP-и PyTorch вазнҳо, градиентҳо ва ҳолатҳои оптимизаторро байни GPU-ҳо тақсим мекунанд; ҳар GPU вазнҳои қабати лозимиро танҳо барои вақти ҳисоби он аз дигарон мегирад. Хотира барои ҳар GPU қариб ба шумораи GPU-ҳо тақсим мешавад.
  • Параллелсозии тензорӣ (tensor parallelism; Megatron-LM, NVIDIA, 2019). Матритсаҳои ҳар қабат байни GPU-ҳо тақсим мешаванд: масалан сарҳои диққат ё сутунҳои қабати пешрав. Мубодилаи зиёди маълумот мехоҳад, пас одатан дар дохили як сервер бо пайвасти тези NVLink истифода мешавад.
  • Параллелсозии қубурӣ (pipeline parallelism; GPipe, Google, 2018). Қабатҳо ба марҳилаҳо тақсим шуда, ҳар марҳила дар GPU-и худ аст; микробастаҳо мисли конвейер аз марҳила ба марҳила мегузаранд, то GPU-ҳо бекор наистанд.
  • Параллелсозии пайдарпайӣ ё контекстӣ. Худи пайдарпайии хеле дароз байни GPU-ҳо тақсим мешавад; масалан Ring Attention блокҳои K ва V-ро дар доира аз GPU ба GPU мегузаронад.
  • Параллелсозии коршиносон — коршиносони MoE дар GPU-ҳои гуногун.

Моделҳои бузург ҳамаи инҳоро якҷоя истифода мебаранд (параллелсозии 3D ва бештар): тензорӣ дар дохили сервер, қубурӣ байни серверҳо ва маълумот дар болои ҳама. Барои аксари мо DDP ё FSDP кифоя аст. Скелети DDP чунин аст:

import os
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader
from torch.utils.data.distributed import DistributedSampler

dist.init_process_group("nccl")
rank = int(os.environ["LOCAL_RANK"])
torch.cuda.set_device(rank)
model = DDP(model.to(rank), device_ids=[rank])
sampler = DistributedSampler(train_set)  # ҳар GPU қисми худи маълумотро мегирад
train_loader = DataLoader(train_set, batch_size=32, sampler=sampler)
[...]  # даври омӯзиши одӣ; DDP градиентҳоро худаш миёна мекунад
dist.destroy_process_group()

Скрипт бо torchrun --nproc_per_node=4 train.py барои чор GPU оғоз мешавад; torchrun ҳар равандро бо LOCAL_RANK-и худ месозад. Китобхонаи Accelerate-и Hugging Face ҳамин кодро бе тағйир дар як GPU, чанд GPU, FSDP ё DeepSpeed иҷро мекунад: accelerate config ва баъд accelerate launch train.py. Trainer дар дохил Accelerate-ро истифода мебарад, пас аллакай омодаи омӯзиши тақсимшуда аст.

Ҳамин тавр, трансформерҳоро метавон ҳам ҳангоми истифода (кеши KV, рамзкушоии тахминӣ, GQA, FlashAttention, MoE) ва ҳам ҳангоми омӯзиш (LoRA, нуқтаҳои назоратӣ, бастабандӣ, ҷамъкунии градиентҳо, параллелсозӣ) хеле тезтар ва арзонтар кард. Бисёре аз ин усулҳо дар китобхонаҳо бо як аргумент фаъол мешаванд — муҳим донистани он аст, ки кадомаш кадом мушкилро ҳал мекунад: суръати хотира, ҳаҷми хотира ё шумораи амалҳо. Дар боби навбатӣ ба моделҳои тавлидии дигар — автоэнкодерҳо, GAN-ҳо ва моделҳои диффузионӣ — мегузарем.

пешина
аз 20
навбатӣ