Въпроси за интервю за инженер по LLM и генеративен AI
15 подбрани въпроса за интервю по LLM и генеративен AI, групирани по ниво на опит. Използвайте ги, за да преговорите основите, практическите компромиси и аргументацията на Senior ниво за производствена среда.
1Обяснете субдумната токенизация и защо тя е предпочитана пред токенизация на ниво думи или символи в съвременните езикови модели.
Субдумната токенизация е хибриден подход за сегментиране на текст, който разбива текста на морфологични сегменти с променлива дължина или поднизове, базирани на честота (като 'un', 'break', 'able'), вместо цели думи или отделни символи. Алгоритми като Byte-Pair Encoding (BPE), WordPiece и Unigram LM научават речник с фиксиран размер от корпус за обучение, където често срещаните думи остават непокътнати като единични токени, докато редките или невиждани думи се разлагат на известни субдумни единици. Субдумната токенизация е предпочитана в съвременните езикови модели, защото балансира размера на речника, дължината на поредицата и устойчивостта на термини извън речника (OOV – Out-Of-Vocabulary). Чистата токенизация на ниво думи изисква прекалено голям речник (водещ до огромни матрици за вграждане – embedding matrices) и все още страда от OOV токени, които се картографират към общи '[UNK]' токени. Обратно, чистата токенизация на ниво символи елиминира OOV проблеми, но води до много дълги поредици, които драстично увеличават изчислителната сложност в механизмите за внимание (attention mechanisms) (които се мащабират квадратично с дължината на поредицата) и намаляват семантичната плътност на токен. Субдумната токенизация постига оптимален компромис, като поддържа дължините на поредиците управляеми, размерите на речника практични (обикновено от 32k до 128k токена) и OOV честотите до нула (особено когато се комбинират с резервни варианти на байтово ниво).
2Обяснете разликата между статични вграждания на думи, контекстуални вграждания и скрити състояния на трансформатор.
Статичните вграждания на думи, контекстуалните вграждания и скритите състояния на трансформатор представляват прогресивни еволюции в начина, по който текстовите представяния улавят значението и синтактичния контекст. 1. Статични вграждания на думи (напр. Word2Vec, GloVe, FastText) присвояват един-единствен, фиксиран вектор на всеки токен от речника, независимо от контекста му в изречението. В тази парадигма многозначни думи като 'bank' (речен бряг срещу финансова банка) или 'apple' (плод срещу технологична компания) имат идентични векторни представяния във всички контексти, разчитайки на статична таблица за търсене. 2. Контекстуални вграждания (напр. ранни ELMo, BERT представяния на токени или вграждания на изречения от Bi-Encoders) произвеждат представяния, при които векторът за токен е динамична функция на заобикалящия го контекст. В BERT или ELMo, 'bank' в 'river bank' получава напълно различен векторен ембединг от 'bank' в 'deposit money at the bank'. 3. Скритите състояния на трансформатор се отнасят до междинните векторни представяния, произведени на всеки индивидуален слой на трансформаторна мрежа по време на прав проход. При дадени входни вграждания на токени на слой 0, всеки следващ трансформаторен слой прилага механизъм за самовнимание и трансформации с пряка връзка, произвеждайки последователност от вектори на скрито състояние `h_l` на слой `l`. Докато скритите състояния на последния слой действат като контекстуални вграждания на високо ниво, по-долните и средните скрити състояния улавят нискоуровневи синтактични, лексикални и структурни характеристики. По този начин скритите състояния на трансформатор обхващат целия вертикален континуум от представяния слой по слой в мрежата.
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())
3Обяснете какво представлява пространството на вграждане (embedding space) и как се интерпретира косинусовата прилика (cosine similarity) за текстови вграждания (text embeddings).
Пространство на вграждане (embedding space) е непрекъснато, многомерно векторно пространство R^d, където дискретни текстови обекти (думи, изречения или документи) се картографират така, че семантични, синтактични или релационни прилики съответстват на геометрична близост и посочни отношения. В това пространство разстоянията и ъглите отразяват семантичната свързаност. Косинусовата прилика измерва косинуса на ъгъла тета между два вектора u и v, изчислен като: Cosine Similarity(u, v) = (u . v) / (||u|| ||v||)
Косинусовата прилика се интерпретира при текстови вграждания, както следва:
- Диапазон и Ориентация: Тя произвежда скаларна стойност, обикновено ограничена в [-1, 1] (или [0, 1] за неотрицателни вграждания). Стойност, близка до 1.0, показва, че двата вектора сочат практически в една и съща посока, отразявайки висока семантична прилика или тематично съответствие. Стойност, близка до 0.0, предполага ортогоналност (семантична независимост или несвързаност), а отрицателните стойности показват противоположни ориентации.
- Инвариантност към големина: За разлика от Евклидовото разстояние (`Euclidean distance`) (L2 разстояние) или скаларното произведение (`dot product`), косинусовата прилика нормализира по отношение на дължината на вектора. При текстовите вграждания, големината на вектора понякога може да корелира с дължината на последователността, честотата на токена или специфичността на термина. Чрез фокусиране изцяло върху посочното подравняване, косинусовата прилика изолира семантичната ориентация от разликите в големината на вектора.
4Обяснете разликата между вграждания на токени, позиционни вграждания и вграждания на сегменти или типове във входовете на трансформаторни модели.
При входовете на трансформаторните модели (особено архитектури от тип BERT), входното представяне за всеки токен обикновено се формира чрез поелементно сумиране на три различни вектора на вграждане: 1. **Вграждания на токени (Token Embeddings)**: Преобразуват дискретни идентификатори на токени от речника в плътни вектори, представящи основната семантична и лексикална идентичност на токените. 2. **Позиционни вграждания (Positional Embeddings)**: Вмъкват информация за реда на токените и последователния им индекс в представянето, компенсирайки факта, че самовниманието (self-attention) е по своята същност пермутационно-инвариантно. 3. **Вграждания на сегменти (или типове токени) (Segment (or Token Type) Embeddings)**: Разграничават различни текстови обхвати или изречения, групирани в една входна последователност (като Изречение А срещу Изречение Б при задачи за сдвоена класификация или отговаряне на въпроси). Комбинирането на тези вграждания осигурява единен плътен входен тензор, кодиращ значението на токена, позицията и групирането на последователността, преди да бъде подаден към първия трансформаторен слой.
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)
5Обяснете вниманието (attention) като механизъм за свързване на токени в последователност, включително заявки (queries), ключове (keys), стойности (values) и многоглаво внимание (multi-head attention).
Вниманието е механизъм, който позволява на токените в последователност динамично да маршрутизират информация и да претеглят релевантността на всички други токени въз основа на контекстуално съвпадение. Линейни проекции преобразуват входа на всеки токен в три вектора:
- Заявка (Q): Представлява каква информация търси текущият токен.
- Ключ (K): Представлява какви атрибути или съдържание предлага токенът, за да съвпада със заявките.
- Стойност (V): Съдържа действителния информационен полезен товар, който трябва да бъде агрегиран.
При мащабираното внимание с точков продукт (scaled dot-product attention), резултатите от вниманието се изчисляват чрез умножаване на Заявки и Ключове ($Q K^T$), мащабирани с $\frac{1}{\sqrt{d_k}}$, за да се предотврати изчезване на градиента при големи измерения, и нормализирани с функцията softmax. Крайният изход е претеглената сума от Стойности:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
Многоглавото внимание (Multi-Head Attention - MHA) проектира Q, K и V в множество независими представителни подпространства (глави) паралелно. Това позволява на модела едновременно да обръща внимание на различни типове взаимоотношения (напр., синтактична структура, кореференция, зависимости на дълги разстояния) между позициите. Изходите на главите се конкатенират и линейно се проектират обратно към измерението на модела.
6Обяснете как маскирането на вниманието (attention masking) се различава при каузалните декодери спрямо двупосочните енкодери и какви поведения позволява или предотвратява.
Маскирането на вниманието контролира кои токени могат да обръщат внимание на кои други токени, като задава attention logit-ите (оценки преди softmax) на $-\infty$ за забранени двойки, осигурявайки, че тяхната тежест на вниманието след softmax е стриктно 0. 1. Каузални декодери (напр. GPT, LLaMA): Използват долно-триъгълна каузална (авторегресивна) маска. Токен на позиция $i$ може да обръща внимание само на позиции $j \le i$. Това предотвратява вниманието към бъдещи токени, позволявайки авторегресивно генериране токен по токен по време на инференция и предотвратявайки изтичане на етикети на бъдещи токени по време на паралелизирано обучение. 2. Двупосочни енкодери (напр. BERT): Не използват каузална маска; всеки токен може да обръща внимание на всички минали и бъдещи токени в последователността. Те използват маски за запълване (padding masks), за да предотвратят обръщането на внимание от валидни токени към празни `[PAD]` токени в партидни последователности. Двупосочното внимание произвежда пълноценни контекстуални представяния, идеални за задачи за разбиране, но предотвратява директно еднопасово авторегресивно генериране на текст.
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)
7Обяснете разликата между архитектурите на трансформатори само с енкодер, само с декодер и енкодер-декодер за езикови задачи.
Трите основни архитектури на трансформатори се различават фундаментално по своите модели за маскиране на вниманието и целеви оперативни цели: 1. Само с енкодер (напр. BERT, RoBERTa): Използва двупосочно самовнимание, при което всеки токен може да обърне внимание на всички други токени в последователността едновременно. Той произвежда богати контекстуални представяния за цяла входяща последователност, което го прави идеален за класификация, екстрактивно QA и представяне на характеристики. Не може естествено да генерира авторегресивен текст. 2. Само с декодер (напр. GPT-3, Llama, Mistral): Използва каузално (еднопосочно) самовнимание, при което токен $i$ може да обърне внимание само на токени на позиции $j \le i$. Той се обучава авторегресивно, използвайки предсказване на следващ токен и служи като стандартна архитектура за генеративни езикови модели, генериране на код и отворен разговор. 3. Енкодер-Декодер (напр. T5, BART): Комбинира двупосочен енкодер с авторегресивен каузален декодер. В допълнение към каузалното самовнимание върху генерираните токени, декодерът използва слоеве за кръстосано внимание, които запитват изходните представяния на енкодера. Тази архитектура е специално създадена за задачи за трансформация от последователност към последователност, като превод и резюмиране.
8Обяснете как изборът на токенизатор на байтово ниво, осъзнаващ Unicode, и многоезичен токенизатор влияе върху качеството на модела, разходите и справедливостта между езиците.
Изборът на дизайна на токенизатора — като сегментация на байтово ниво спрямо осъзнаваща Unicode и разпределения на многоезичен речник — пряко влияе върху качеството на модела, разходите за инференция/обучение и езиковата справедливост.
По отношение на разходите и справедливостта, токенизатори, обучени предимно на английски или латински корпуси, разпределят по-голямата част от записите в речника на английски думи и морфеми. Следователно, английският език постига висока компресия (напр. ~1.3 токена на дума), докато нелатинските писмености (напр. арабски, деванагари, тайски, китайски) или езици с малко ресурси често се фрагментират на множество поддуми или сурови UTF-8 байтове (често 3 до 6 токена на дума). Това несъответствие често се нарича „такса за токени“ (*token tax*) или „дисбаланс в коефициента на плодовитост“ (*fertility rate imbalance*): неанглоговорящите потребители плащат значително повече за единица семантично съдържание при таксуване на API, изчерпват лимитите на контекстния прозорец много по-бързо и страдат от по-висока латентност.
По отношение на качеството, токенизаторите на байтово ниво (като Byte-level BPE (Byte Pair Encoding) в GPT-2/GPT-4 или SentencePiece с байт *fallback* в LLaMA) избягват сривове от непознати символи и грешки от типа out-of-vocabulary (UNK) изцяло, защото всеки валиден UTF-8 низ се декомпозира на байтови токени. Въпреки това, прекомерното байтово фрагментиране влошава качеството на представяне, защото трансформърът трябва да изразходва слоеве, за да рекомбинира байтовите фрагменти в семантични концепции, преди да извърши разсъждения на високо ниво. Увеличаването на размера на многоезичния речник (напр. разширяване от 32k до 128k+ токена) балансира коефициентите на плодовитост и подобрява производителността на задачите надолу по веригата при различни езици, за сметка на умерено по-голям входен/изходен *embedding* слой.
9Какви артефакти от токенизация се появяват при числени разсъждения, генериране на код или редки Unicode текстове и как специализираните токенизатори могат да ги намалят?
Артефактите от токенизация възникват, когато поддумови (subword) токенизатори разделят структуриран, числов или рядък текст непоследователно, което пречи на модела да разпознае скритата семантична или синтактична закономерност. Ключовите артефакти включват: 1. Артефакти при числени разсъждения: Стандартните BPE (Byte Pair Encoding) токенизатори, обучени върху общ текст, разделят числата на произволни части въз основа на честота (напр., '12345' може да се токенизира като ['12', '345'], докато '12346' се токенизира като ['123', '46']). Това непоследователно групиране нарушава подравняването по позиционна стойност (единици, десетици, стотици) и затруднява аритметичните разсъждения. 2. Артефакти при генериране на код: Отстъпите (водещи интервали/табове) и многосимволните оператори (напр. '==', '!=', '->') често се разделят нередовно през границите на интервални символи, което води до грешки в отстъпите, набъбнали бройки токени в дълбоко отстъпен код и синтактични корупции. 3. Артефакти при редки Unicode и емоджита: Многобайтовите UTF-8 (Unicode Transformation Format - 8-bit) последователности (като сложни емоджита със съединители с нулева ширина или редки скриптове) се разделят на сурови байтови токени, които не носят индивидуално семантично значение, което причинява халюцинирани символи или повредено изобразяване на глифове при генериране. Специализираните токенизатори намаляват тези артефакти, използвайки персонализирани правила за предварителна токенизация и ограничения на речника: - Разделяне на цифри: Налагането на токенизация по една цифра (напр. разделяне с регулярен израз на всяка цифра `0-9` в собствен токен) осигурява еднообразно представяне на позиционна стойност за математически разсъждения. - Специализирани токени за интервали/отстъпи: Добавяне на явни токени за отстъпи от множество интервали (напр. 2, 4, 8 интервала) и запазване на ключови думи/оператори на езици за програмиране. - Предварителна токенизация с регулярен израз / резервни варианти на байтово ниво: Използване на разделители с регулярен израз (като регулярните изрази на GPT-4/tiktoken), които разделят препинателни знаци, букви и числа в строги категории, преди да бъдат изчислени BPE сливанията, предотвратявайки сливания между категории (напр. предотвратяване на сливането на 'a=10' в един токен).
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)])
10Разсъдете върху компромисите при механизма за внимание (attention) с дълъг контекст, включително квадратично пълно внимание, плъзгащо-прозоречно внимание, разредено или глобално внимание, цена на KV-кеша и разреждане на вниманието.
Мащабирането на вниманието към прозорци с дълъг контекст представя компромиси между изчисленията, заеманата памет и прецизността на модела: 1. Квадратично пълно внимание спрямо плъзгащо-прозоречно / разредено внимание: Стандартното пълно внимание се мащабира квадратично ($O(N^2)$) по отношение на изчислителните ресурси и паметта за активации с дължината на последователността $N$. Плъзгащо-прозоречното (локално) внимание ограничава вниманието до фиксиран съседен район $W$, намалявайки сложността до $O(N \cdot W)$, но изисква множество слоеве за разпространяване на информация между отдалечени токени. Разредените или глобални модели на внимание комбинират локални прозорци с избрани глобални "котвени" токени, за да запазят $O(N)$ мащабиране, като същевременно позволяват комуникация на дълги разстояния. 2. Цена на паметта на KV-кеша: По време на авторегресивно генериране, ключовете и стойностите за всички предишни токени се кешират, за да се избегнат излишни изчисления. Паметта на KV-кеша се мащабира линейно с дължината на последователността ($O(B \cdot L \cdot H_{KV} \cdot D \cdot N)$). За много дълги контексти (32k–128k+ токени), KV-кешът консумира десетки гигабайта GPU (Graphics Processing Unit) VRAM (Video Random Access Memory) на партида (batch), което стеснява максималния размер на партидата и пропускателната способност на паметта. 3. Разреждане на вниманието (изгубено в средата): С нарастването на контекста, знаменателят на softmax сумира десетки хиляди токени, разпространявайки вероятностната маса тънко върху несвързан контекст. Това увеличаване на ентропията разрежда остротата на вниманието, влошавайки способността на модела надеждно да си спомня специфична информация, вградена в средата на дълги подсказвания (prompts).
11Сравнете MHA, MQA и GQA и обяснете как те влияят на паметта на KV-кеша и пропускателната способност на декодиране.
Механизмът на внимание с множество глави (Multi-Head Attention - MHA), вниманието с множество заявки (Multi-Query Attention - MQA) и вниманието с групирани заявки (Grouped-Query Attention - GQA) се различават по начина, по който главите за ключ ($K$) и стойност ($V$) се споделят между главите за заявки ($Q$):
1. **MHA (Multi-Head Attention):** Има равен брой $Q$, $K$ и $V$ глави ($H_Q = H_{KV}$, съотношение 1:1). Всяка глава за заявки обръща внимание на собствените си независими представяния на ключ/стойност. Въпреки че е изразително, то изисква кеширане на отделни KV матрици за всяка глава.
2. **MQA (Multi-Query Attention):** Използва множество $Q$ глави ($H$), но само 1 споделена $K$ глава и 1 споделена $V$ глава (съотношение $H:1$). Това намалява размера на KV кеша с коефициент $H$, но може да доведе до лека загуба на качество или нестабилност при обучението.
3. **GQA (Grouped-Query Attention):** Групира $Q$ главите в $G$ дяла, където всяка група споделя една $K$ и една $V$ глава (напр. 8 $Q$ глави на една KV глава). GQA предлага оптимален компромис, възстановявайки на практика цялото качество на моделиране на MHA, като същевременно запазва предимствата на MQA по отношение на паметта.
**Въздействие върху KV-кеша и пропускателната способност на декодирането:**
Авторегресивното генериране на токени (декодиране) е ограничено от пропускателната способност на паметта, тъй като графичният процесор (GPU) трябва да прехвърля целия KV кеш от високоскоростна памет (High-Bandwidth Memory - HBM) към вградена в чипа SRAM за всеки генериран токен. Чрез намаляване на броя на KV главите с $H/G$ (напр. $4\times$ до $8\times$ в GQA или $32\times+$ в MQA):
- Заеманата памет от KV-кеша намалява пропорционално, което позволява много по-големи размери на пакети при обслужване във VRAM на GPU.
- Трафикът за четене на памет от HBM на токен спада значително, драматично увеличавайки пропускателната способност на токените при декодиране.
# 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}%")
12Обяснете как FlashAttention ускорява точното изчисление на механизма на вниманието, без да променя резултатите от вниманието.
FlashAttention ускорява изчислението на механизма на вниманието, като прави алгоритъма съобразен с входно-изходните операции (I/O) — минимизирайки трафика за четене и запис на паметта между бавната високоскоростна памет за графични процесори (HBM) и бързата вградена в чипа SRAM, вместо да се опитва да намали общия брой аритметични FLOP (операции с плаваща запетая). Стандартният механизъм на вниманието материализира междинни N x N матрици на оценката на вниманието и вероятността в HBM, което причинява сериозно тясно място в пропускателната способност на паметта. FlashAttention преодолява това чрез три ключови механизма: 1. Разделяне на блокове (Tiling): Разделя матриците Query, Key и Value на блокове, които се побират изцяло в SRAM паметта, вградена в GPU. 2. Онлайн Softmax: Изчислява softmax инкрементално върху блокове, като проследява текущите максимуми и суми на нормализатори, актуализирайки частични резултати, без да е необходима цялата материализирана N x N матрица в паметта. 3. Точно преизчисляване: По време на обратния проход не чете съхранени междинни матрици на вниманието от HBM; вместо това ги преизчислява в движение в SRAM от съхранените текущи статистики. Тъй като не се използват апроксимации, нискорангови факторизации или евристики за изпускане на токени, резултатът е математически точен до числова точност с плаваща запетая, докато отпечатъкът на HBM паметта е намален от O(N^2) до O(N).
13Проектирайте детерминистични агентни работни потоци, използвайки планировчици, машини на състоянията, DAG (насочени ациклични графи), типизирано междинно състояние, ограничени повторни опити и верификация на резултатите от инструменти, вместо отворени агентни цикли.
Отворените агентни цикли (напр. неограничени автономни ReAct цикли) в производство често страдат от недетерминистично разклоняване, безкрайни цикли, неконтролируемо изразходване на токени и дрифт на състоянието. Детерминистичният агентен работен поток заменя свободно оформените цикли със структуриран, наблюдаем поток на управление: 1. Машини на състоянията и DAG (насочени ациклични графи): Потокът на управление се дефинира като изричен Насочен ацикличен граф или крайна машина на състоянията (напр. LangGraph, Temporal, AWS Step Functions). Преходите на възлите зависят от изрични условия и типизирани резултати, вместо отворени решения на модела. 2. Типизирано междинно състояние: Състоянието, споделено между възлите, се моделира със строги схеми (напр. Pydantic модели или класове за данни). Възлите извършват валидирани операции за четене и запис, предотвратявайки дрифт на схемата или неправилно формирано състояние. 3. Планировчици: Структурираните планировчици издават ограничен план предварително (напр. подреден списък от стъпки, подкрепени от изброяване) или избират от ограничен набор от валидни преходи на състоянието, вместо свободно да решават следващи действия без ограничения. 4. Верификация на резултатите от инструменти: Изходите, върнати от инструментите, се валидират детерминистично спрямо схеми и бизнес правила, преди да актуализират състоянието или да ги предадат на следващи стъпки на големия езиков модел (LLM). 5. Ограничени повторни опити и резервни механизми: Всяка стъпка налага изрични бюджети за повторни опити, експоненциални забавяния, времеви ограничения и резервни преходи (напр. ескалиране до човешки преглед или задействане на безопасно въздържане), за да се гарантира терминиране.
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
14Проектирайте политика за доверие и въздържане от отговор за асистент с голям езиков модел (LLM), отговарящ на въпроси от регулирани домейни.
В регулирани домейни (като здравеопазване, банкиране, право и съответствие) некоректните отговори водят до регулаторни санкции, правна отговорност и рискове за безопасността. Надеждната политика за доверие и въздържане от отговор съчетава многосигнален калибриран резултат за доверие, многостепенни прагове за отговор и детерминирани работни потоци за ескалация:
1. **Калибриране на доверието чрез множество сигнали:** Суровите logprobs на LLM често са неправилно калибрирани при заявки извън домейна. Резултатът за доверие трябва да синтезира множество независими сигнали:
* **Резултат за заземяване при извличане:** Семантична прилика и доверие при пренареждане на извлечени фрагменти от доказателства.
* **Логическо следване на ниво твърдение (NLI – Natural Language Inference):** Модели за логическо следване на естествен език, които проверяват дали всяко извлечено твърдение следва от извлечения изходен контекст.
* **Семантична ентропия / Самосъгласуваност:** Измерване на семантичната съгласуваност между множество генерирани примери.
* **Logprobs на токени от модела:** Минимални и средни logprobs за ключови именувани същности и фактически токени.
2. **Многостепенна политика за въздържане от отговор:**
* **Високо доверие (Резултат >= Висок праг):** Директно предоставяне на генерирания отговор с вградени цитати.
* **Средно доверие / Двусмисленост (Нисък праг <= Резултат < Висок праг):** Предоставяне на консервативен отговор с изрични предупреждения, откази от отговорност или искане на уточняващи детайли от потребителя.
* **Ниско доверие / Извън обхват (Резултат < Нисък праг):** Пълно въздържане от отговор със стандартизирано съобщение за отказ.
3. **Ескалация и възможност за одит на съответствието:**
* **Детерминирана ескалация:** Въздържанията или критичните несъответствия се маршрутизират автоматично към опашки за човек в цикъла (HITL) или системи за билети на агенти с пълен контекст.
* **Одитен запис и произход:** Цялата телеметрия – включително хешове на заявките, ID на извлечени документи, индивидуални резултати на компонентите за доверие и окончателни решения за маршрутизация – трябва да се записва за одит за съответствие с регулациите.
15Проектирайте стратегия за маршрутизация на модели, която избира между малки, средни и големи модели въз основа на сложността на заявката, цената, риска и изискванията за качество.
Производствена архитектура за маршрутизация на модели насочва входящите заявки през малки (напр. 1B–8B малки езикови модели (SLM)), средни (напр. 14B–70B модели) и големи (напр. водещи или големи модели на смес от експерти (MoE)) слоеве, като балансира сложността, латентността, риска и изчислителната цена. Работният поток за маршрутизация обикновено съчетава статични правила, предиктивна маршрутизация и динамични каскади за връщане назад:
1. **Детерминирани/статични портали за политики:** Филтриране на заявки по клиентски слой, строги споразумения за ниво на обслужване (SLA) относно латентността, регулаторен/доменен риск (напр. медицинска диагноза или правно формулиране, маршрутизирани директно към модели от най-висок клас) или прости задачи, съобразени с правила (напр. основен регулярен израз/форматиране към малки модели).
2. **Предиктивна маршрутизация по сложност:** Бърз, лек класификатор (като търсене на прилика на вграждания, кръстосан енкодер или малък SLM рутер) оценява сложността на заявката, дълбочината на разсъжденията и доменната двусмисленост, за да избере най-рентабилния слой предварително.
3. **Динамично изпълнение и каскади за ескалация:** Изпращане на подкана първо към по-малък модел и оценка на доверието в изхода (чрез логаритмични вероятности/ентропия на токени, валидност на структурирана схема или проверки за защитни парапети). Ако доверието е под прага или валидацията се проваля, рутерът ескалира към среден или голям модел. Ключовите компромиси в системата включват допълнително закъснение на рутера спрямо спестявания от изчисления, бюджети за изчакване при отпадане при пикове на трафика и непрекъсната оценка (напр. сенчеста оценка за проследяване на отклоненията в качеството на изхода между слоевете).
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)