Подготовка к собеседованию по LLM и генеративному ИИ

Вопросы для собеседования инженера по LLM и генеративному ИИ

15 отобранных вопросов для собеседования по LLM и генеративному ИИ, сгруппированных по уровням подготовки. Используйте их для повторения основ, понимания практических компромиссов и рассуждений на уровне Senior-специалиста в производственной среде.

Начать ИИ-собеседование по LLM и генеративному ИИКредитная карта не требуется. Доступна 1 бесплатная сессия.
Практика технических собеседований на английскомРежим, в котором не носители языка могут потренироваться проходить интервью.

Вопросы для Junior-специалистов

1Что такое субсловная токенизация и почему её используют в языковых моделях?

BPE, WordPiece или Unigram делят текст на единицы переменной длины. Частые слова могут быть отдельными токенами, редкие составляются из частей. Это уменьшает словарь относительно разбиения по словам и обычно сокращает последовательность относительно символов. Побайтовый резерв позволяет представить неизвестные слова и символы, если он предусмотрен. Без такого механизма отсутствие токенов всё ещё возможно.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('gpt2')
text = 'unbelievable'
tokens = tokenizer.tokenize(text)
token_ids = tokenizer.encode(text)

print('Tokens:', tokens)
print('Token IDs:', token_ids)
Попробовать ответить на вопрос AI-тренеру

2Чем статические эмбеддинги слов отличаются от контекстных представлений и скрытых состояний трансформера?

Статическое представление слова не меняется по контексту, поэтому два употребления одного слова получают один вектор. Контекстное представление зависит от окружения. Скрытые состояния трансформера — векторы позиций на каждом слое после преобразований внимания и других операций. Из выбранных состояний можно получить контекстные эмбеддинги токенов или агрегировать их для текста. Свойства слоёв зависят от модели и задачи.

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())
Попробовать ответить на вопрос AI-тренеру

3Что представляет пространство эмбеддингов и как понимать косинусное сходство текстов?

Модель отображает тексты в векторы так, чтобы геометрия помогала решать обучающую задачу. Косинусное сходство ненулевых векторов равно их скалярному произведению, делённому на произведение норм. Оно лежит от −1 до 1 и измеряет совпадение направлений, не учитывая длину. Большое значение часто указывает на близость по критериям модели. Ноль не доказывает независимость по смыслу, а отрицательное значение не означает антонимию. Пороги проверяйте на своих данных.

import numpy as np

def cosine_sim(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Hypothetical 3D embeddings
v_king = np.array([0.9, 0.1, 0.4])
v_queen = np.array([0.85, 0.15, 0.42])
v_apple = np.array([0.1, 0.9, -0.2])

print('Sim(king, queen):', round(cosine_sim(v_king, v_queen), 4))
print('Sim(king, apple):', round(cosine_sim(v_king, v_apple), 4))
Попробовать ответить на вопрос AI-тренеру

4Объясните различие токенных, позиционных и сегментных эмбеддингов входа transformer.

В BERT-подобных моделях представление токена обычно складывается поэлементно из трёх векторов. Токенный переводит ID словаря в плотный вектор лексической и смысловой идентичности. Позиционный добавляет порядок и индекс: немаскированное self-attention без позиции эквивариантно перестановке — перестановка входов переставляет выходы и сама порядка не кодирует. Другие архитектуры вместо суммы используют rotary/relative-механизмы. Segment/token-type отличает фрагменты или предложения одной последовательности, например A/B в парной классификации и QA. Сумма кодирует значение, позицию и группу в одном плотном тензоре до первого слоя.

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)
Попробовать ответить на вопрос AI-тренеру

5Как механизм внимания связывает позиции последовательности и для чего нужны запросы, ключи, значения и несколько голов?

Запрос Q позиции сравнивается с ключами K доступных позиций. После деления оценок на √dₖ и применения маски softmax превращает оценки в веса. Выход — взвешенная сумма значений V. Масштабирование сдерживает рост разброса оценок и насыщение softmax. Несколько голов используют разные обучаемые проекции, а их выходы объединяются и преобразуются. Головы могут учиться разным связям, но фиксированная специализация им не назначается.

import torch
import torch.nn.functional as F

# Q, K, V: [batch_size, seq_len, head_dim]
Q = torch.randn(1, 4, 64)
K = torch.randn(1, 4, 64)
V = torch.randn(1, 4, 64)
d_k = Q.size(-1)

scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)

print("Output shape:", output.shape)
Попробовать ответить на вопрос AI-тренеру

6Чем каузальная маска внимания отличается от внимания двунаправленного энкодера?

Каузальная маска разрешает позиции смотреть на себя и предшествующие позиции, исключая будущий текст. Это позволяет обучать прогноз следующего токена параллельно по позициям без утечки ответа. Двунаправленный энкодер обычно использует контекст слева и справа, сохраняя маску заполнителей и другие ограничения задачи. Его представления рассчитаны на доступный вход, а для обычной авторегрессивной генерации нужен каузальный путь.

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)
Попробовать ответить на вопрос AI-тренеру

7Чем отличаются трансформеры с энкодером, с декодером и с парой энкодер–декодер?

Энкодер обычно видит контекст с обеих сторон и строит представления для классификации или извлечения ответа. Каузальный декодер предсказывает продолжение, используя предыдущие позиции. В архитектуре энкодер–декодер энкодер обрабатывает вход, а декодер авторегрессивно строит выход и через перекрёстное внимание читает представления входа. Это удобно, например, для перевода и суммаризации.

# Causal mask (Decoder-Only) vs Full mask (Encoder-Only)
import torch

seq_len = 4
encoder_mask = torch.ones(seq_len, seq_len)  # Full bidirectional attention
decoder_causal_mask = torch.tril(torch.ones(seq_len, seq_len))  # Lower-triangular

print("Encoder Mask:
", encoder_mask)
print("Decoder Causal Mask:
", decoder_causal_mask)
Попробовать ответить на вопрос AI-тренеру

Вопросы для Middle-специалистов

8Как байтовая, Unicode-ориентированная и многоязычная токенизация влияют на стоимость и качество для разных языков?

Словарь, обученный на неравномерном корпусе, может делить одни языки на большее число токенов на сопоставимый текст. Это расходует окно, увеличивает работу и может влиять на качество. Байтовое покрытие позволяет представить незнакомые символы, но редкий текст может распасться на длинные последовательности. Нормализация Unicode и баланс корпуса меняют результат. Сравнивайте число токенов на сопоставимый объём и качество задач по языкам. Больший словарь сам по себе равенства не гарантирует.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('gpt2')

english_text = 'Hello world'
hindi_text = 'नमस्ते दुनिया'

print('English tokens:', tokenizer.tokenize(english_text))
print('Hindi tokens:', tokenizer.tokenize(hindi_text))
print('English token count:', len(tokenizer.encode(english_text)))
print('Hindi token count:', len(tokenizer.encode(hindi_text)))
Попробовать ответить на вопрос AI-тренеру

9Какие проблемы токенизации возникают для чисел, кода и редких символов Unicode и как их уменьшить?

Цифры могут группироваться непоследовательно, что усложняет работу с разрядами. В коде неудачное разбиение отступов и операторов увеличивает длину. Составные символы могут разбиваться на несколько байтовых единиц. Специальные правила для цифр, пробелов и операторов и побайтовый резерв помогают управлять покрытием. Они меняют длину и обучающий сигнал, поэтому проверяйте арифметику, синтаксис и точное восстановление Unicode на целевых данных.

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)])
Попробовать ответить на вопрос AI-тренеру

10Обсудите длинный контекст: квадратичное полное внимание, окна, sparse/global, стоимость KV-кэша и размывание внимания.

1. Dense attention квадратично по длине N по арифметике; явная матрица оценок также требует квадратичной памяти, но точный IO-aware FlashAttention её не материализует. Локальное окно W даёт O(N×W), но дальняя информация проходит несколько слоёв. Sparse может совместить фиксированные окна и ограниченное число global-токенов для линейного масштаба паттерна и дальних связей. 2. При autoregressive-generation K/V прошлых токенов кэшируются; память O(B×L×H_KV×D×N). На 32k–128k+ это десятки ГБ VRAM на batch, ограничение размера пакета и bandwidth. 3. Длинный контекст может добавлять отвлекающие токены и вызывать проблемы, связанные с позицией и обобщением. Lost-in-the-middle — наблюдаемая зависимость извлечения от позиции, а не универсальный результат роста энтропии softmax: внимание может оставаться сосредоточенным на немногих токенах. Измеряйте точность по длинам и позициям доказательств, не выводите её лишь из знаменателя.

def kv_cache_gb(batch_size, seq_len, layers=32, kv_heads=8, head_dim=128, bytes_per_elem=2):
    # 2 for Key and Value
    total_bytes = batch_size * seq_len * layers * kv_heads * head_dim * 2 * bytes_per_elem
    return total_bytes / (1024 ** 3)

print(f"32k context: {kv_cache_gb(4, 32768):.2f} GB")
print(f"128k context: {kv_cache_gb(4, 131072):.2f} GB")
Попробовать ответить на вопрос AI-тренеру

11Чем MHA, MQA и GQA отличаются по кэшу ключей и значений и скорости декодирования?

В MHA каждой голове запроса соответствует своя пара голов ключей и значений. MQA делит одну пару между всеми запросами. GQA делит запросы на группы, у каждой своя пара. При прочих равных размер KV-кэша пропорционален числу таких пар. Меньший кэш может снизить трафик памяти и увеличить ёмкость пакета. Выигрыш и качество зависят от модели, реализации и нагрузки.

# 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}%")
Попробовать ответить на вопрос AI-тренеру

12Как FlashAttention ускоряет точное внимание?

Алгоритм обрабатывает Q, K и V блоками в быстрой памяти и ведёт текущий максимум и нормировку softmax. Он не записывает всю матрицу оценок в глобальную память GPU. При обратном проходе нужные промежуточные значения можно пересчитать. Это уменьшает обмен данными и объём промежуточного хранения. Операция математически эквивалентна полному вниманию, хотя округление может отличаться, а квадратичная арифметическая сложность сохраняется.

import torch

def online_softmax_step(m_prev, l_prev, out_prev, scores_block, v_block):
    # scores_block: (B, H, Br, Bc), v_block: (B, H, Bc, D)
    m_block = scores_block.max(dim=-1, keepdim=True).values
    m_new = torch.maximum(m_prev, m_block)
    
    # Rescale previous and current accumulators
    p_prev_scale = torch.exp(m_prev - m_new)
    p_block = torch.exp(scores_block - m_new)
    
    l_new = p_prev_scale * l_prev + p_block.sum(dim=-1, keepdim=True)
    out_new = (p_prev_scale * l_prev * out_prev + p_block @ v_block) / l_new
    return m_new, l_new, out_new
Попробовать ответить на вопрос AI-тренеру

Вопросы для Senior-специалистов

13Как сделать выполнение процесса с языковой моделью предсказуемым через граф или автомат, типизированное состояние и проверку результатов?

Опишите разрешённые шаги и переходы в графе или конечном автомате. Модель может предложить план из допустимых действий, который приложение проверит до выполнения. Храните состояние в типизированном формате и проверяйте результат каждого инструмента. Ограничьте сроки, повторы и права и предусмотрите конечные состояния и ручное рассмотрение. Управляющая логика может быть детерминированной, даже если вывод модели меняется.

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
Попробовать ответить на вопрос AI-тренеру

14Как задать политику уверенности и воздержания для помощника в регулируемой области?

Определите допустимые задачи и исходы вместе с ответственными специалистами. Проверяйте применимость и актуальность источников, а сигналы уверенности калибруйте на профильной выборке. При нехватке сведений уточняйте запрос, ограничивайте ответ либо передавайте человеку согласно уровню риска. Высокая вероятность или согласие нескольких генераций не заменяют проверку. Для аудита храните минимально необходимые сведения о версиях, источниках и решении.

from dataclasses import dataclass
from typing import Literal

@dataclass
class DecisionResult:
    action: Literal["SERVE", "SERVE_WITH_CAVEAT", "ABSTAIN_AND_ESCALATE"]
    confidence_score: float
    reason: str

def evaluate_confidence_policy(retrieval_score: float, nli_entailment_score: float, semantic_entropy: float) -> DecisionResult:
    # Composite calibrated confidence index [0, 1]
    composite_score = (0.4 * retrieval_score) + (0.4 * nli_entailment_score) + (0.2 * (1.0 - semantic_entropy))
    
    if composite_score >= 0.85:
        return DecisionResult("SERVE", composite_score, "High evidence grounding")
    elif composite_score >= 0.60:
        return DecisionResult("SERVE_WITH_CAVEAT", composite_score, "Partial evidence support")
    else:
        return DecisionResult("ABSTAIN_AND_ESCALATE", composite_score, "Insufficient ground truth")
Попробовать ответить на вопрос AI-тренеру

15Как маршрутизировать запросы между моделями по сложности, цене, риску и качеству?

Сначала задайте допустимые модели и операции по ограничениям задачи. На представительных данных измерьте качество, задержку и цену каждого маршрута. Простые запросы можно отдавать меньшей модели, а неопределённые или не прошедшие проверку — более подходящей в пределах бюджета. Высокий риск может требовать внешней проверки, а не просто большей модели. Следите за долей повторных маршрутов, ошибками и стоимостью успешного ответа.

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)
Попробовать ответить на вопрос AI-тренеру

Собеседования на телефоне: LLM and Generative AI

Отвечайте голосом и получайте обратную связь от ИИ в приложении для Android.

Скачать EngineerSpeak в Google Play
Сканируйте для скачивания