Подготовка к собеседованию по LLM и генеративному ИИ
Вопросы для собеседования инженера по LLM и генеративному ИИ
15 отобранных вопросов для собеседования по LLM и генеративному ИИ, сгруппированных по уровням подготовки. Используйте их для повторения основ, понимания практических компромиссов и рассуждений на уровне Senior-специалиста в производственной среде.
1Объясните субсловную токенизацию и почему она предпочтительнее пословной или посимвольной токенизации в современных языковых моделях.
Субсловная токенизация — это гибридный подход к сегментации текста, который разбивает текст на морфологические фрагменты переменной длины или подстроки, основанные на частоте (например, 'un', 'break', 'able'), а не на целые слова или отдельные символы. Алгоритмы, такие как Byte-Pair Encoding (BPE), WordPiece и Unigram LM, обучают словарь фиксированного размера на обучающем корпусе, где часто встречающиеся слова остаются целыми токенами, в то время как редкие или незнакомые слова разлагаются на известные субсловные единицы. Субсловная токенизация предпочтительна в современных языковых моделях, поскольку она балансирует размер словаря, длину последовательности и устойчивость к словам вне словаря (OOV). Чисто пословная токенизация требует чрезмерно большого словаря (что приводит к огромным матрицам эмбеддингов) и всё ещё страдает от OOV-токенов, отображаемых на универсальные токены '[UNK]'. И наоборот, чисто посимвольная токенизация устраняет проблемы OOV, но приводит к очень длинным последовательностям, которые значительно увеличивают вычислительную сложность в механизмах внимания (которая масштабируется квадратично от длины последовательности) и разбавляют семантическую плотность на токен. Субсловная токенизация обеспечивает оптимальный компромисс, сохраняя длину последовательностей управляемой, размер словаря практичным (обычно от 32k до 128k токенов) и нулевой уровень OOV (особенно в сочетании с байтовыми резервными вариантами).
2Объясните разницу между статическими векторными представлениями слов (word embeddings), контекстными векторными представлениями (contextual embeddings) и скрытыми состояниями трансформера (transformer hidden states).
Статические векторные представления слов, контекстные векторные представления и скрытые состояния трансформера представляют собой последовательные этапы развития того, как текстовые представления захватывают смысл и синтаксический контекст.
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Объясните, что представляет собой пространство эмбеддингов, и как интерпретируется косинусное сходство для текстовых эмбеддингов.
Пространство эмбеддингов — это непрерывное, высокоразмерное векторное пространство `R^d`, куда дискретные текстовые сущности (слова, предложения или документы) проецируются таким образом, что семантическое, синтаксическое или реляционное сходство соответствует геометрической близости и направленным отношениям. В этом пространстве расстояния и углы отражают семантическую взаимосвязь.
Косинусное сходство измеряет косинус угла тета между двумя векторами `u` и `v` и рассчитывается по формуле:
`Cosine Similarity(u, v) = (u . v) / (||u|| ||v||)`
Косинусное сходство интерпретируется в текстовых эмбеддингах следующим образом:
- **Диапазон и ориентация:** Оно даёт скалярное значение, обычно находящееся в диапазоне `[-1, 1]` (или `[0, 1]` для неотрицательных эмбеддингов). Значение, близкое к `1.0`, указывает на то, что два вектора направлены практически в одну сторону, что отражает высокое семантическое сходство или тематическое соответствие. Значение, близкое к `0.0`, подразумевает ортогональность (семантическую независимость или отсутствие связи), а отрицательные значения указывают на противоположные ориентации.
- **Инвариантность к величине:** В отличие от евклидова расстояния (`L2 distance`) или скалярного произведения, косинусное сходство нормализует по длине вектора. В текстовых эмбеддингах величина вектора иногда может коррелировать с длиной последовательности, частотой токенов или специфичностью терминов. Сосредоточившись исключительно на направленном выравнивании, косинусное сходство изолирует семантическую ориентацию от различий в величинах векторов.
4Объясните разницу между вложениями токенов, позиционными вложениями и сегментными вложениями (или вложениями типа) во входных данных трансформера.
Во входных данных трансформеров (особенно в архитектурах BERT-стиля) входное представление для каждого токена обычно формируется путём поэлементного суммирования трёх различных векторов вложений:
1. **Вложения токенов**: Отображают дискретные идентификаторы (ID) токенов словаря в плотные векторы, представляющие основную семантическую и лексическую идентичность токенов.
2. **Позиционные вложения**: Вводят информацию о порядке токенов и их порядковом индексе в представление, компенсируя тот факт, что механизм самовнимания (self-attention) по своей природе инвариантен к перестановкам.
3. **Сегментные вложения** (или вложения типа токена): Различают разные текстовые сегменты или предложения, упакованные в одну входную последовательность (например, Предложение A против Предложения B в задачах парной классификации или ответов на вопросы).
Комбинирование этих вложений формирует единый плотный входной тензор, кодирующий значение токена, его позицию и группировку последовательности перед передачей в первый слой трансформера.
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).
Внимание (attention) — это механизм, который позволяет токенам в последовательности динамически направлять информацию и взвешивать релевантность всех других токенов на основе контекстуального соответствия. Линейные проекции преобразуют входные данные каждого токена в три вектора:
- Запрос (Query, Q): Представляет информацию, которую ищет текущий токен.
- Ключ (Key, K): Представляет атрибуты или содержимое, которые токен предлагает для сопоставления с запросами.
- Значение (Value, V): Содержит фактическую информационную нагрузку (payload), подлежащую агрегированию.
В масштабированном внимании по скалярному произведению (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) контролирует, каким токенам разрешено уделять внимание другим токенам, устанавливая логиты внимания (оценки до 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Объясните разницу между архитектурами трансформеров только с кодировщиком (encoder-only), только с декодировщиком (decoder-only) и с кодировщиком-декодировщиком (encoder-decoder) для языковых задач.
Три основные архитектуры трансформеров принципиально отличаются паттернами маскирования внимания и целевыми операционными задачами:
1. **Только с кодировщиком (Encoder-Only)** (например, BERT, RoBERTa): Использует двунаправленное самовнимание, при котором каждый токен может одновременно обращаться ко всем другим токенам в последовательности. Это позволяет создавать богатые контекстуальные представления для всей входной последовательности, что делает их идеальными для классификации, извлекающего вопрос-ответа (extractive QA) и представления признаков. Они не могут естественным образом генерировать авторегрессивный текст.
2. **Только с декодировщиком (Decoder-Only)** (например, GPT-3, Llama, Mistral): Использует причинное (однонаправленное) самовнимание, где токен $i$ может обращаться только к токенам на позициях $j \le i$. Он обучается авторегрессивно с использованием предсказания следующего токена и служит стандартной архитектурой для генеративных языковых моделей, генерации кода и открытых диалогов.
3. **Кодировщик-Декодировщик (Encoder-Decoder)** (например, T5, BART): Сочетает двунаправленный кодировщик с авторегрессивным причинным декодировщиком. В дополнение к причинному самовниманию над сгенерированными токенами, декодировщик использует слои перекрестного внимания, которые запрашивают выходные представления кодировщика. Эта архитектура специально создана для задач преобразования последовательности в последовательность, таких как перевод и суммаризация.
8Объясните, как выбор байтовых, учитывающих Unicode и многоязыковых токенизаторов влияет на качество модели, стоимость и справедливость для разных языков.
Выбор дизайна токенизатора — такой как сегментация на уровне байтов (byte-level) или с учетом Unicode (Unicode-aware) и распределение многоязыкового словаря — напрямую влияет на качество последующей модели, стоимость вывода/обучения и лингвистическое равноправие. С точки зрения стоимости и справедливости, токенизаторы, обученные преимущественно на корпусах английского языка или языков с латинским алфавитом, выделяют большинство словарных записей английским словам и морфемам. Следовательно, английский язык достигает высокой степени сжатия (например, ~1.3 токена на слово), тогда как нелатинские алфавиты (например, арабский, деванагари, тайский, китайский) или низкоресурсные языки часто фрагментируются на множество подслов или чистых UTF-8 байтов (часто от 3 до 6 токенов на слово). Это несоответствие часто называют «налогом на токены» или «дисбалансом плотности токенов»: пользователи неанглоязычных языков платят значительно больше за единицу семантического контента при расчетах через API, намного быстрее исчерпывают лимиты контекстного окна и сталкиваются с более высокой задержкой. С точки зрения качества, байтовые токенизаторы (такие как Byte-level BPE в GPT-2/GPT-4 или SentencePiece с байтовым резервом в LLaMA) полностью избегают сбоев из-за необработанных символов и ошибок типа out-of-vocabulary (UNK), поскольку любая допустимая строка UTF-8 разлагается на байтовые токены. Однако чрезмерная фрагментация на байты ухудшает качество представления, потому что трансформеру приходится тратить слои на recombining байтовых фрагментов в семантические концепции, прежде чем выполнять высокоуровневые рассуждения. Увеличение размера многоязыкового словаря (например, расширение с 32k до 128k+ токенов) балансирует плотность токенов и улучшает производительность модели в последующих задачах для различных языков, ценой умеренно большего входного/выходного слоя встраивания.
9Какие артефакты токенизации появляются при числовых рассуждениях, генерации кода или с редкими символами Unicode, и как специализированные токенизаторы могут их уменьшить?
Артефакты токенизации возникают, когда сабворд-токенизаторы (subword tokenizers) непоследовательно разбивают структурированный, числовой или редкий текст, мешая модели распознавать базовую семантическую или синтаксическую регулярность. К основным артефактам относятся:
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Обсудите компромиссы при использовании внимания с длинным контекстом, включая квадратичное полное внимание, внимание со скользящим окном, разреженное или глобальное внимание, стоимость KV-кеша и размывание внимания.
Масштабирование механизма внимания для длинных контекстных окон представляет собой компромиссы между вычислительной мощностью, объемом памяти и точностью модели:
1. **Квадратичное полное внимание против внимания со скользящим окном / разреженного внимания:** Стандартное полное внимание масштабируется квадратично ($O(N^2)$) по вычислениям и памяти активаций в зависимости от длины последовательности $N$. Внимание со скользящим окном (локальное) ограничивает внимание фиксированным диапазоном $W$, уменьшая сложность до $O(N \cdot W)$, но требует нескольких слоев для распространения информации между удаленными токенами. Разреженные или глобальные паттерны внимания комбинируют локальные окна с выбранными глобальными опорными токенами для сохранения масштабирования $O(N)$, обеспечивая при этом дальнее взаимодействие.
2. **Стоимость памяти KV-кеша (Key-Value cache):** Во время авторегрессивной генерации ключи и значения для всех предыдущих токенов кешируются, чтобы избежать избыточных вычислений. Память KV-кеша масштабируется линейно с длиной последовательности ($O(B \cdot L \cdot H_{KV} \cdot D \cdot N)$). Для очень длинных контекстов (32k–128k+ токенов) KV-кеш потребляет десятки гигабайт GPU VRAM (видеопамять графического процессора) на пакет, что ограничивает максимальный размер пакета и пропускную способность памяти.
3. **Размывание внимания (эффект "потеряно в середине"):** По мере увеличения контекста знаменатель функции softmax суммирует десятки тысяч токенов, тонко распределяя вероятностную массу по нерелевантному контексту. Это увеличение энтропии снижает четкость внимания, ухудшая способность модели надежно вспоминать конкретную информацию, встроенную в середине длинных промптов.
11Сравните MHA, MQA и GQA и объясните, как они влияют на память KV-кэша и пропускную способность декодирования.
Multi-Head Attention (MHA), Multi-Query Attention (MQA) и Grouped-Query Attention (GQA) различаются тем, как головы Key ($K$) и Value ($V$) распределяются между головами Query ($Q$):
1. **Multi-Head Attention (MHA)**: Имеет равное количество голов $Q$, $K$ и $V$ ($H_Q = H_{KV}$, соотношение 1:1). Каждая голова запроса использует свои независимые представления ключей/значений. Хотя этот подход выразителен, он требует кэширования отдельных KV-матриц для каждой головы.
2. **Multi-Query Attention (MQA)**: Использует несколько голов $Q$ ($H$), но только 1 общую голову $K$ и 1 общую голову $V$ (соотношение $H:1$). Это сокращает размер KV-кэша в $H$ раз, но может привести к небольшой потере качества или нестабильности обучения.
3. **Grouped-Query Attention (GQA)**: Группирует головы $Q$ на $G$ разделов, где каждая группа использует одну голову $K$ и одну голову $V$ (например, 8 голов $Q$ на одну KV-голову). GQA предлагает оптимальный компромисс, восстанавливая практически всё качество моделирования MHA при сохранении преимуществ MQA по памяти.
**Влияние на KV-кэш и пропускную способность декодирования:**
Авторегрессивная генерация токенов (декодирование) ограничена пропускной способностью памяти, поскольку GPU должен передавать весь KV-кэш из высокоскоростной памяти (HBM - High-Bandwidth Memory) во встроенную SRAM (Static Random-Access Memory) для каждого сгенерированного токена. Сокращая количество KV-голов на $H/G$ (например, в 4-8 раз в GQA или в 32+ раза в MQA):
* Объём памяти, занимаемый KV-кэшем, сокращается пропорционально, что позволяет использовать значительно большие пакетные размеры для обслуживания в GPU VRAM.
* Трафик чтения из 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 ускоряет вычисление внимания, делая алгоритм оптимизированным под операции ввода-вывода (IO-aware) — минимизируя трафик чтения и записи памяти между медленной высокоскоростной памятью GPU (HBM) и быстрой встроенной памятью SRAM, а не пытаясь уменьшить общее количество арифметических операций FLOP. Стандартный механизм внимания материализует промежуточные матрицы оценок внимания и вероятностей размером N x N в HBM, что вызывает серьезное узкое место пропускной способности памяти. FlashAttention преодолевает это с помощью трех ключевых механизмов: 1. **Тайлинг (Tiling):** Он разделяет матрицы Query, Key и Value на блоки, которые полностью помещаются во встроенную память SRAM GPU. 2. **Онлайн-Softmax (Online Softmax):** Он вычисляет softmax инкрементально по блокам, отслеживая текущие максимумы и суммы нормализаторов, обновляя частичные выходы без необходимости материализовать полную матрицу N x N в памяти. 3. **Точный перерасчет (Exact Recomputation):** Во время обратного прохода он не считывает сохраненные промежуточные матрицы внимания из HBM; вместо этого он пересчитывает их на лету в SRAM на основе сохраненных текущих статистик. Поскольку не используются аппроксимации, низкоранговые факторизации или эвристики отбрасывания токенов, выход является математически точным с точностью до чисел с плавающей запятой, при этом уменьшая объем занимаемой памяти HBM с O(N^2) до O(N).
13Разработайте детерминированные агентные рабочие процессы, используя планировщики, конечные автоматы, DAG (Directed Acyclic Graph), типизированное промежуточное состояние, ограниченные повторные попытки и верификацию результатов инструмента вместо открытых агентных циклов.
Открытые агентные циклы (например, неограниченные автономные циклы ReAct) в продакшене часто страдают от недетерминированного ветвления, бесконечных циклов, неконтролируемых расходов токенов и дрейфа состояния. Детерминированный агентный рабочий процесс заменяет свободные циклы структурированным, наблюдаемым потоком управления:
1. **Конечные автоматы и DAG (Directed Acyclic Graph):** Поток управления определяется как явный направленный ациклический граф или конечный автомат (например, LangGraph, Temporal, AWS Step Functions). Переходы узлов зависят от явных условий и типизированных результатов, а не от открытых решений модели.
2. **Типизированное промежуточное состояние:** Состояние, разделяемое между узлами, моделируется с использованием строгих схем (например, модели Pydantic или классы данных). Узлы выполняют валидированные операции чтения и записи, предотвращая дрейф схемы или некорректное состояние.
3. **Планировщики:** Структурированные планировщики заранее выдают ограниченный план (например, упорядоченный список шагов на основе перечислений) или выбирают из ограниченного набора допустимых переходов состояний, а не свободно принимают решения о следующих действиях без ограничений.
4. **Верификация результатов инструмента:** Выходные данные, возвращаемые инструментами, детерминированно валидируются по отношению к схемам и бизнес-правилам перед обновлением состояния или передачей на следующие шаги LLM (Large Language Model).
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 часто бывают плохо откалиброваны для запросов вне предметной области. Оценка уверенности должна синтезировать несколько независимых сигналов:
* **Оценка обоснованности извлечения (Retrieval Grounding Score)**: Семантическое сходство и уверенность в переранжировании извлеченных фрагментов доказательств.
* **Логическое следование на уровне утверждений (Claim-Level Entailment (NLI))**: Модели логического следования естественного языка (NLI (Natural Language Inference)), проверяющие, что каждое извлеченное утверждение логически следует из извлеченного исходного контекста.
* **Семантическая энтропия / Самосогласованность (Semantic Entropy / Self-Consistency)**: Измерение семантической согласованности по нескольким сгенерированным выборкам.
* **Логарифмические вероятности токенов модели (Model Token Logprobs)**: Минимальные и средние logprobs для ключевых именованных сущностей и фактических токенов.
2. **Многоуровневая политика воздержания (Tiered Abstention Policy)**:
* **Высокая уверенность (оценка >= высокий порог)**: Непосредственная выдача сгенерированного ответа со встроенными цитатами.
* **Средняя уверенность / Неоднозначность (низкий порог <= оценка < высокий порог)**: Выдача консервативного ответа с явными оговорками, отказами от ответственности или запрос у пользователя уточняющих деталей.
* **Низкая уверенность / Вне области применимости (оценка < низкий порог)**: Жесткое воздержание с использованием стандартизированного сообщения об отказе.
3. **Эскалация и возможность аудита соответствия (Escalation and Compliance Auditability)**:
* **Детерминированная эскалация**: Воздержания или критические расхождения автоматически направляются в очереди с участием человека (human-in-the-loop (HITL)) или в системы обработки заявок агентами с полным контекстом.
* **Журнал аудита и происхождение данных (Audit Trail & Lineage)**: Полная телеметрия, включая хеши промптов, идентификаторы извлеченных документов, оценки отдельных компонентов уверенности и окончательные решения о маршрутизации, должна быть занесена в журнал для возможности регуляторного аудита.
15Разработайте стратегию маршрутизации моделей, которая выбирает между малыми, средними и большими моделями на основе сложности запроса, стоимости, рисков и требований к качеству.
Архитектура маршрутизации моделей в производстве направляет входящие запросы между малыми (например, малые языковые модели (SLM) 1B–8B), средними (например, модели 14B–70B) и большими (например, передовые или крупные модели Mixture of Experts (MoE)) уровнями, балансируя сложность, задержку, риски и вычислительные затраты. Рабочий процесс маршрутизации обычно сочетает статические правила, предиктивную маршрутизацию и динамические каскады отката:
1. **Детерминированные/статические шлюзы политик**: Фильтрация запросов по уровню клиента, жестким соглашениям об уровне обслуживания (SLA) по задержке, регуляторным/доменным рискам (например, медицинская диагностика или юридическое составление документов, маршрутизируемые напрямую к моделям высшего уровня), или простым задачам, сопоставляемым по правилам (например, базовые регулярные выражения/форматирование к малым моделям).
2. **Предиктивная маршрутизация по сложности**: Быстрый, легковесный классификатор (такой как поиск по сходству эмбеддингов, кросс-энкодер или небольшой маршрутизатор на основе SLM) оценивает сложность запроса, глубину рассуждений и доменную неоднозначность, чтобы заранее выбрать наиболее экономически эффективный уровень.
3. **Динамические каскады выполнения и эскалации**: Отправка промпта сначала в меньшую модель и оценка уверенности вывода (с помощью логарифмических вероятностей токенов (logprobs)/энтропии, валидности структурированной схемы или проверок защитных барьеров). Если уверенность ниже порога или валидация не удалась, маршрутизатор эскалирует запрос к средней или большой модели. Ключевые компромиссы системы включают накладные расходы на задержку маршрутизатора по сравнению с экономией вычислительных ресурсов, бюджеты таймаутов отката при пиковых нагрузках трафика и непрерывную оценку (например, теневую оценку для отслеживания дрейфа качества вывода между уровнями).
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)