Перейти к содержимому
Ступень 99.1 Мультимодальные модели

Модели «зрение + язык»

Как картинка превращается в токены (ViT), как CLIP учит картинки и тексты жить в одном пространстве, как устроены VLM вроде Qwen-VL и InternVL и что такое «оптическое сжатие контекста» DeepSeek-OCR.

УРОКstage-9/9-1-multimodal/01-vision-language.mdx2 чУрок 1 из 5
После урока вы сможете
  • Объяснить, как ViT превращает картинку в последовательность токенов, и посчитать число визуальных токенов для конкретной модели
  • Реализовать контрастивный loss CLIP (InfoNCE) и объяснить, зачем в нём температура
  • Описать архитектуру VLM «энкодер + проектор + LLM» и узнать её в Qwen-VL, InternVL и DeepSeek-OCR
  • Оценить идею оптического сжатия контекста — и критику этой идеи

Всё, что мы строили до сих пор, работало с текстом. Но флагманы 2026 года — Qwen3.5, Kimi K2.5, MiniMax M3 — нативно мультимодальныемультимодальныйEN multimodal: они читают скриншоты, графики, сканы договоров и кадры видео. Агент из модуля 8.3, который «видит» экран, и RAG из модуля 8.2 по PDF с таблицами — это всё работа зрения.

Хорошая новость: нового «зоопарка» архитектур учить не придётся. Картинку режут на кусочки, каждый кусочек превращают в вектор — и дальше работает тот же трансформер, что вы собрали на ступени 4. Весь урок — про три идеи: как сделать из картинки токены (ViT), как научить эти токены «понимать» язык (CLIP) и как пристегнуть их к LLM (VLM). В конце разберём свежую и спорную идею DeepSeek: хранить длинный текст в виде картинки.

Трансформер умеет работать с последовательностью векторов. Текст мы превращали в последовательность токенов (урок 4.2). Для картинки авторы Vision Transformer (ViT)визуальный трансформер (ViT)EN Vision Transformer (ViT)картинка режется на патчи, каждый патч становится токеном (Google, 2020) предложили самое простое решение — название статьи говорит само за себя: «Картинка стоит 16×16 слов».

  1. Режем картинку на квадратные патчипатч изображенияEN image patchквадратный кусок картинки (обычно 14×14 или 16×16 пикселей), «слово» для ViT, например 16×16 пикселей.
  2. Каждый патч вытягиваем в вектор: 16⋅16⋅3=76816 \cdot 16 \cdot 3 = 768 чисел (три канала RGB).
  3. Умножаем на обучаемую матрицу W∈R768×dW \in \mathbb{R}^{768 \times d} — получаем эмбеддингэмбеддинг, векторное представлениеEN embedding патча.
  4. Прибавляем позиционный эмбеддинг (иначе модель не знает, где был патч — та же проблема, что в уроке 4.5.2).
  5. Подаём последовательность в обычный энкодер трансформера. Маски нет: каждый патч «смотрит» на все остальные.

Для картинки 224×224 и патча 16 получаем сетку 14×14=19614 \times 14 = 196 патчей. Это 196 «слов» — короткая фраза по меркам LLM.

Число визуальных токенов — главный параметр стоимости VLM: они занимают место в контексте и в KV-кэшеKV-кэшEN KV cache точно так же, как текст.

Nпатчей=Hp⋅WpN_{\text{патчей}} = \frac{H}{p} \cdot \frac{W}{p}
МодельВходПатчПатчейСжатиеТокенов в LLM
ViT-B/16224×22416196—196
CLIP ViT-L/14 (LLaVA-1.5)336×33614576—576
Qwen2-VL224×22414256слияние 2×264 + 2 служебных = 66
InternVL 1.5 (один тайл)448×448141024pixel shuffle, в 4 раза256
DeepSeek-OCR, режим Base1024×1024164096свёрточное, в 16 раз256

Обратите внимание на последнюю строку. Без сжатия страница документа в 1024×1024 дала бы 4096 токенов, а внимание стоит O(N2)O(N^2): это 16.7 млн пар «патч — патч» на один слой. Поэтому почти все современные VLM так или иначе сжимают визуальные токены.

В реальном коде ViT вы не найдёте цикла по патчам. Нарезка плюс линейный слой — это ровно свёртка с ядром pp и шагом pp: окно свёртки не перекрывается и «смотрит» на один патч (размерности свёртки и её запись через матричное умножение — в уроке 3.5.1). Проверим, что два способа дают одно и то же:

import torch
import torch.nn as nn
torch.manual_seed(0)
# Картинка: батч из 1, 3 канала (RGB), 224×224 пикселя
img = torch.randn(1, 3, 224, 224)
p, d = 16, 768 # размер патча и ширина эмбеддинга (как в ViT-B/16)
# Способ 1: «руками» — нарезать на патчи и умножить на матрицу
# unfold(ось, размер, шаг) режет ось на окна: ось высоты (2) → 14 окон по 16, затем ось ширины (3) — так же
patches = img.unfold(2, p, p).unfold(3, p, p) # (1, 3, 14, 14, 16, 16)
patches = patches.permute(0, 2, 3, 1, 4, 5) # (1, 14, 14, 3, 16, 16)
patches = patches.reshape(1, 14 * 14, 3 * p * p) # (1, 196, 768)
proj = nn.Linear(3 * p * p, d)
tokens_a = proj(patches)
# Способ 2: свёртка с ядром p и шагом p — так пишут в реальном коде ViT
conv = nn.Conv2d(3, d, kernel_size=p, stride=p)
with torch.no_grad(): # переносим те же веса в свёртку
conv.weight.copy_(proj.weight.view(d, 3, p, p))
conv.bias.copy_(proj.bias)
tokens_b = conv(img).flatten(2).transpose(1, 2) # (1, 768, 14, 14) → (1, 196, 768)
print("форма токенов:", tuple(tokens_a.shape))
print("одинаково?", torch.allclose(tokens_a, tokens_b, atol=1e-4))
print("параметров в patch embedding:", sum(t.numel() for t in conv.parameters()))
форма токенов: (1, 196, 768)
одинаково? True
параметров в patch embedding: 590592

590 592=768⋅768+768590\,592 = 768 \cdot 768 + 768: матрица плюс сдвиг. Если allclose вернул False, почти наверняка перепутан порядок осей в permute — патч должен вытягиваться в порядке «канал, строка, столбец», как веса свёртки.

CLIP: учим картинки и тексты говорить на одном языке

Заголовок раздела «CLIP: учим картинки и тексты говорить на одном языке»

ViT выдаёт векторы, но откуда модель узнает, что вектор фотографии панды как-то связан со словом «панда»? Классический путь — разметить картинки по 1000 классам (ImageNet). Это дорого и узко: модель выучит «panda» как класс № 388, но не поймёт фразу «панда спит на дереве».

CLIPCLIP (контрастивное предобучение «картинка — текст»)EN Contrastive Language–Image Pre-training (CLIP) (OpenAI, 2021) взял другой источник сигнала: 400 млн пар «картинка — подпись», собранных в интернете. Два энкодера — для картинок (ViT или ResNet) и для текста (трансформер) — учатся так, чтобы эмбеддинги картинки и её подписи оказались рядом, а с чужими подписями — далеко. Это и есть контрастивное обучениеконтрастивное обучениеEN contrastive learningсближаем представления «правильных пар» и раздвигаем «неправильные».

flowchart LR
I["Батч из B картинок"] --> VE["Визуальный энкодер (ViT)"] --> IE["B векторов картинок"]
T["B подписей"] --> TE["Текстовый энкодер"] --> TT["B векторов текстов"]
IE --> S["Матрица сходств B×B"]
TT --> S
S --> L["Правильные пары — на диагонали: cross-entropy по строкам и столбцам"]
Контрастивное обучение CLIP

Возьмём батч из BB пар. Нормируем все векторы до длины 1 и посчитаем матрицу косинусных сходствкосинусное сходствоEN cosine similarityКосинус угла между векторами — скалярное произведение, делённое на произведение длин. Для нормированных векторов равно скалярному произведению. sij=⟨vi,tj⟩s_{ij} = \langle \mathbf{v}_i, \mathbf{t}_j \rangle: картинка ii против подписи jj. Для картинки ii правильная подпись одна — с тем же номером. Значит, каждая строка матрицы — это задача классификации на BB классов, и мы уже знаем loss для неё — cross-entropy из урока 1.3:

Limg→txt=−1B∑i=1Blog⁡esii/τ∑j=1Besij/τ\mathcal{L}_{\text{img}\to\text{txt}} = -\frac{1}{B}\sum_{i=1}^{B} \log \frac{e^{s_{ii}/\tau}}{\sum_{j=1}^{B} e^{s_{ij}/\tau}}

То же самое по столбцам даёт Ltxt→img\mathcal{L}_{\text{txt}\to\text{img}} («какая картинка к этой подписи?»). Итоговый InfoNCEInfoNCE (контрастивная функция потерь)EN InfoNCE losscross-entropy по матрице сходств, где правильные пары стоят на диагонали — среднее двух: L=12(Limg→txt+Ltxt→img)\mathcal{L} = \tfrac{1}{2}(\mathcal{L}_{\text{img}\to\text{txt}} + \mathcal{L}_{\text{txt}\to\text{img}}). Эту формулу вы уже видели в уроке 8.2.1: так обучают эмбеддинг-модели для RAG, только там пары «запрос — фрагмент», а здесь «картинка — подпись». А сама идея — притянуть настоящую пару и оттолкнуть негативы — пришла из негативного сэмплирования word2vec (урок 4.3.2).

Что здесь написано словами: «вероятность, которую модель дала правильной паре среди всех BB кандидатов, должна быть высокой». Остальные B−1B - 1 подписей в батче служат негативами. CLIP обучали с батчем 32 768 — каждая картинка соревновалась с 32 767 чужими подписями. Чем больше негативов, тем тоньше различия, которые модель вынуждена выучить.

Три фотографии, три подписи — одна по-английски. Эмбеддинги зададим руками:

import numpy as np
# Игрушечные «эмбеддинги» (в CLIP их выдают энкодеры; здесь задаём руками, D = 4)
images = np.array([
[0.9, 0.1, 0.0, 0.1], # фото: кошка на диване
[0.1, 0.9, 0.2, 0.0], # фото: панда ест бамбук
[0.0, 0.2, 0.9, 0.3], # фото: схема метро Пекина
])
texts = np.array([
[0.8, 0.2, 0.1, 0.0], # «кошка на диване»
[0.2, 0.8, 0.0, 0.1], # «panda eating bamboo» (панда ест бамбук)
[0.1, 0.1, 0.8, 0.4], # «карта метро»
])
def l2n(x):
return x / np.linalg.norm(x, axis=1, keepdims=True)
def softmax_rows(z):
z = z - z.max(axis=1, keepdims=True)
e = np.exp(z)
return e / e.sum(axis=1, keepdims=True)
sim = l2n(images) @ l2n(texts).T # косинусные сходства 3×3
print("сходства:\n", np.round(sim, 3))
for T in (1.0, 0.07):
p = softmax_rows(sim / T) # строка i: «какая подпись к картинке i?»
q = softmax_rows(sim.T / T) # строка j: «какая картинка к подписи j?»
loss = (-np.mean(np.log(np.diag(p))) - np.mean(np.log(np.diag(q)))) / 2
print(f"T = {T}: P(правильная подпись) = {np.round(np.diag(p), 3)}, loss = {loss:.4f}")
сходства:
[[0.978 0.357 0.17 ]
[0.363 0.961 0.31 ]
[0.161 0.236 0.98 ]]
T = 1.0: P(правильная подпись) = [0.504 0.483 0.522], loss = 0.6880
T = 0.07: P(правильная подпись) = [1. 1. 1.], loss = 0.0002

Диагональ явно больше остальных — «модель» всё различает правильно. Но при τ=1\tau = 1 вероятность правильной подписи всего ≈0.5, а loss 0.69 далёк от нуля. Причина: косинус живёт в [−1,1][-1, 1], и разница между 0.98 и 0.36 для softmax слишком мала (урок 1.3: разница логитов в 1 даёт отношение вероятностей всего e≈2.7e \approx 2.7). Деление на τ=0.07\tau = 0.07 растягивает шкалу в 14 раз. В CLIP температура обучаемая: стартует с 0.07, а множитель 1/τ1/\tau ограничен сверху числом 100, чтобы обучение не разогналось.

Обученный CLIP умеет классифицировать без единого размеченного примера. Пишем для каждого класса подпись «фото кошки», «фото панды», …, кодируем их текстовым энкодером и выбираем ту, что ближе к картинке. Так CLIP без обучения на 1.28 млн размеченных картинок ImageNet сравнялся по точности с ResNet-50, обученным на них «с учителем». На том же приёме — сравнить эмбеддинги текста и картинки — построен поиск картинок по тексту и RAGгенерация с дополнением извлечёнными данными (RAG)EN retrieval-augmented generation (RAG) по документам с иллюстрациями: поиск «по смыслу» между модальностями.

CLIP сопоставляет картинку и текст, но не умеет отвечать на вопросы. VLM (визуально-языковая модель)визуально-языковая модель (VLM)EN vision-language model (VLM)в китайских статьях также «мультимодальная большая модель» (MLLM) добавляет к визуальному энкодеру языковую модель. Схема, которую в 2023 году популяризовала LLaVA и которую с вариациями используют почти все открытые модели:

flowchart LR
IMG["Картинка"] --> VIT["Визуальный энкодер (ViT из CLIP)"]
VIT --> PRJ["Проектор (MLP)"]
PRJ --> CAT["Склейка последовательности"]
TXT["Текст: «Что на картинке?»"] --> EMB["Эмбеддинги LLM"] --> CAT
CAT --> LLM["LLM (Qwen, InternLM, DeepSeek…)"] --> OUT["Ответ токен за токеном"]
Типичная VLM
  1. Визуальный энкодер — обычно ViT, предобученный как CLIP. Его выходы уже «пахнут языком».
  2. Проекторпроектор (визуальный адаптер)EN projector, vision-language connectorпереводит выходы визуального энкодера в пространство эмбеддингов LLM — маленькая сеть (часто двухслойный MLP), которая переводит векторы энкодера в пространство эмбеддингов LLM. У ViT и LLM разная ширина и разные «системы координат».
  3. LLM — та же модель, что в текстовом чате. Визуальные токены для неё — просто ещё несколько векторов в начале последовательности. Loss считается только на токенах ответа, как при SFT в уроке 6.1.2.

Обучают обычно в два этапа. Сначала замораживают энкодер и LLM и учат только проектор на парах «картинка — подпись» (выравнивание). Затем размораживают LLM и учат на диалогах с картинками (visual instruction tuning).

Вот как это выглядит в коде — с приёмом Qwen2-VL, где 4 соседних патча (квадрат 2×2) склеиваются в один токен:

import torch
import torch.nn as nn
torch.manual_seed(0)
d_vis, d_llm = 768, 896 # ширина ViT-B и ширина маленькой LLM (у Qwen2.5-0.5B hidden_size = 896)
vis_tokens = torch.randn(1, 196, d_vis) # выход визуального энкодера: сетка 14×14 патчей
# 1) Сжатие 2×2 → 1 (как в Qwen2-VL): склеиваем 4 соседних патча в один вектор
g = 14
x = vis_tokens.view(1, g // 2, 2, g // 2, 2, d_vis) # (1, 7, 2, 7, 2, 768)
x = x.permute(0, 1, 3, 2, 4, 5).reshape(1, 49, 4 * d_vis) # (1, 49, 3072)
# 2) Проектор: двухслойный MLP переводит «язык зрения» в пространство эмбеддингов LLM
projector = nn.Sequential(nn.Linear(4 * d_vis, d_llm), nn.GELU(), nn.Linear(d_llm, d_llm))
img_emb = projector(x) # (1, 49, 896)
# 3) Текстовые эмбеддинги (в настоящей модели — nn.Embedding LLM)
text_emb = torch.randn(1, 12, d_llm) # «Что изображено на картинке?» ≈ 12 токенов
# 4) Склеиваем по оси последовательности — дальше это обычный вход трансформера
seq = torch.cat([img_emb, text_emb], dim=1)
print("после ViT: ", tuple(vis_tokens.shape))
print("после 2×2-слияния:", tuple(x.shape))
print("после проектора:", tuple(img_emb.shape))
print("вход LLM: ", tuple(seq.shape))
print("параметров в проекторе:", sum(p.numel() for p in projector.parameters()))
после ViT: (1, 196, 768)
после 2×2-слияния: (1, 49, 3072)
после проектора: (1, 49, 896)
вход LLM: (1, 61, 896)
параметров в проекторе: 3557120

Проектор — 3.6 млн параметров, капля по сравнению с LLM. Слияние 2×2 сократило визуальные токены в 4 раза, а информацию не выбросило: она переехала из длины последовательности в ширину вектора (768 → 3072).

ViT из CLIP обучен на картинках 224 или 336 пикселей. Сожмите до 336×336 скан договора — и мелкий текст превратится в кашу. Стандартом стали два решения, и оба массово распространили китайские модели:

  • Динамическое разрешение (Qwen2-VL, 2024). Картинка подаётся в родном размере: большая даёт много токенов, маленькая — мало. Чтобы трансформер понимал, где патч, Qwen придумал M-RoPE: позиция токена раскладывается на три компоненты — время, высота, ширина — и каждая вращает свою часть вектора (вспомните RoPE из урока 4.5). Тот же механизм работает для видео. Саму идею «ViT в родном разрешении» предложила Google в NaViT (2023); Qwen2-VL сделала её стандартом открытых VLM.
  • Нарезка на тайлы (InternVL 1.5, 2024). Картинка режется на 1–40 тайлов по 448×448 пикселей в зависимости от пропорций, плюс уменьшенная копия целиком (thumbnail). Каждый тайл — 1024 патча, после pixel shuffle — 256 токенов. Pixel shuffle — та же идея, что слияние 2×2 в коде выше: четыре соседних токена складываются в один вектор вчетверо большей ширины.
МодельЛабораторияКогдаЧто важно
Qwen-VLAlibaba08.2023энкодер ViT-bigG; один слой cross-attention с 256 обучаемыми запросами сжимает картинку 448×448 до 256 токенов; умеет указывать рамки объектов
InternVLShanghai AI Lab12.2023визуальный энкодер InternViT-6B — «вырастили» зрение до размеров, сравнимых с LLM
InternVL 1.5Shanghai AI Lab04.2024тайлы 448×448 (до 4K), pixel shuffle, двуязычные данные с упором на документы и китайский OCR
Qwen2-VLAlibaba09.2024динамическое разрешение, M-RoPE, слияние 2×2, видео; размеры от 2B до 72B
Qwen2.5-VLAlibaba02.2025окно внимания в ViT для больших картинок, документы, видео, агенты для GUI
DeepSeek-OCRDeepSeek10.2025оптическое сжатие контекста (ниже)
Kimi K2.5, Qwen3.5, MiniMax M3Moonshot, Alibaba, MiniMax2026зрение встроено во флагманскую модель, а не в отдельную «-VL»-версию

Тренд хорошо виден по таблице: от «LLM + пристёгнутый CLIP» к моделям, где картинки — равноправная часть претрейна.

Длинный контекст дорог: KV-кэш растёт линейно, внимание — квадратично (урок 4.6.4). А теперь заметьте странную вещь. Страница текста — это ~1000 текстовых токенов. Та же страница в виде картинки, как мы видели, может занять 100–256 визуальных токенов. Что, если хранить старый контекст картинкой? Это вопрос, который DeepSeek поставила в статье 2025 года: сколько визуальных токенов нужно, чтобы модель могла восстановить NN текстовых?

Мерило — степень оптического сжатияоптическое сжатие контекстаEN optical context compressionидея DeepSeek-OCR — хранить длинный текст как картинку в меньшем числе визуальных токенов:

сжатие=число текстовых токенов на страницечисло визуальных токенов\text{сжатие} = \frac{\text{число текстовых токенов на странице}}{\text{число визуальных токенов}}

Проверяют через оптическое распознавание символовоптическое распознавание символов (OCR)EN optical character recognition (OCR): если модель может прочитать текст с картинки, информация в визуальных токенах сохранилась.

  • DeepEncoder (≈380M параметров) — последовательно соединены SAM-base (≈80M; энкодер из модели сегментации Segment Anything от Meta, внимание в окнах — дёшево видит мелкие детали) → свёрточный компрессор, сокращающий токены в 16 раз → CLIP-large (≈300M, глобальное внимание — видит страницу целиком). Дорогое глобальное внимание работает уже по сжатым токенам.
  • Декодер — DeepSeek-3B-MoE с ≈570M активных параметров (вспомните MoE из модуля 5.3).
РежимРазрешениеВизуальных токенов
Tiny512×51264
Small640×640100
Base1024×1024256
Large1280×1280400
Gundamnn тайлов 640×640 + обзор 1024×1024100n+256100n + 256
  • При сжатии меньше 10× точность распознавания ≈97%; при 20× — около 60%.
  • На бенчмарке разбора документов OmniDocBench модель обходит GOT-OCR2.0 (256 токенов на страницу), используя 100 токенов, и MinerU2.0 (в среднем 6000+ токенов на страницу), используя меньше 800. Обе системы — тоже китайские: GOT-OCR2.0 сделали в StepFun, MinerU — в Shanghai AI Lab.
  • В продакшене одна A100-40G размечает 200 тыс.+ страниц в день — это генератор обучающих данных для LLM и VLM.

Самая смелая часть — идея «забывания»: старые реплики диалога хранить картинками всё меньшего разрешения, как человеческая память, в которой давние события постепенно размываются. В статье это направление для исследований, а не проверенный результат.

Через полтора месяца исследователи из UC San Diego выложили статью с говорящим названием «Optical Context Compression Is Just (Bad) Autoencoding» (arXiv:2512.03643). Они сравнили визуальный энкодер DeepSeek-OCR с почти бесплатным усреднением (mean pooling) соседних текстовых эмбеддингов и с обученным иерархическим энкодером. Вывод: на задаче восстановления текста простые методы не хуже или лучше картинок при любой степени сжатия, а «энтузиазм вокруг оптического сжатия опережает доказательства».

Это не значит, что DeepSeek-OCR плохая модель — как OCR-система она отличная. Это значит, что заголовок «картинки сжимают текст лучше токенов» требует более строгого сравнения. Как читать такие споры — тема урока 9.2.2. В январе 2026 года DeepSeek выпустила DeepSeek-OCR 2 (arXiv:2601.20552): новый энкодер переупорядочивает визуальные токены по смыслу страницы, а не построчно «слева направо, сверху вниз».

Реализуйте две главные операции урока на NumPy: нарезку картинки на патчи (без циклов, через reshape и transpose) и симметричный InfoNCE. Тесты проверят форму и порядок патчей, устойчивость loss к крошечной температуре и его симметричность.

Одно отличие от кода на PyTorch выше: там картинка хранилась как (C, H, W) — так принято в PyTorch, — а здесь как (H, W, C), как её отдают NumPy и библиотеки чтения картинок. Поэтому внутри патча числа идут в порядке «строка, столбец, канал». На результат это не влияет, пока порядок одинаков для всех патчей.

Патчи ViT и контрастивный loss CLIPnumpy · нужен интернет8 тестов
import numpy as np


def patchify(img, p):
    """Режет картинку формы (H, W, C) на квадратные патчи p×p и вытягивает каждый в вектор.

    Возвращает массив формы (N, p*p*C), где N = (H // p) * (W // p).
    Порядок патчей — построчно: слева направо, сверху вниз (как мы читаем текст).
    Внутри патча порядок такой же, как у img[i:i+p, j:j+p, :].reshape(-1).
    Считаем, что H и W делятся на p без остатка.
    """
    # TODO: проще всего через reshape → transpose → reshape (без циклов)
    ...


def l2_normalize(x, eps=1e-8):
    """Делит каждую строку x (форма (B, D)) на её L2-норму: строки становятся единичными векторами."""
    # TODO
    ...


def info_nce(img_emb, txt_emb, temperature=0.07):
    """Симметричный контрастивный loss CLIP (InfoNCE) для батча из B пар «картинка — подпись».

    img_emb, txt_emb — массивы формы (B, D); правильная пара i — это img_emb[i] и txt_emb[i].
    Шаги:
      1) нормируем строки обоих массивов (l2_normalize);
      2) logits = img @ txt.T / temperature  — матрица B×B косинусных сходств;
      3) loss_i2t — средняя cross-entropy по СТРОКАМ (у строки i правильный класс — i);
         loss_t2i — то же самое по СТОЛБЦАМ (это строки матрицы logits.T);
      4) вернуть (loss_i2t + loss_t2i) / 2 как float.
    Считайте устойчиво: log-sum-exp с вычитанием максимума (как в уроке 1.3).
    """
    # TODO
    ...


# Проверьте себя кнопкой «Запустить»:
img = np.arange(4 * 4 * 3, dtype=float).reshape(4, 4, 3)  # «картинка» 4×4, 3 канала
out = patchify(img, 2)
print("patchify ещё не реализована" if out is None else out.shape)  # ожидается (4, 12)

rng = np.random.default_rng(0)
a = rng.normal(size=(8, 16))
print(info_nce(a, a + 0.1 * rng.normal(size=(8, 16))))  # маленькое число: пары почти совпадают
print(info_nce(a, rng.normal(size=(8, 16))))  # большое число: пары случайные
Проверь себя6 вопросов
1 CLIP ViT-L/14 получает картинку 336×336. Сколько визуальных токенов (патчей) он выдаст, не считая служебного [CLS]?
2 Зачем в контрастивном loss CLIP косинусные сходства делят на температуру τ≈0.07\tau \approx 0.07?
3 Батч из 8 пар, все эмбеддинги картинок и текстов одинаковые. Чему равен InfoNCE (натуральный логарифм)? Округлите до двух знаков.
4 Какие утверждения о VLM вида «энкодер + проектор + LLM» верны?Можно выбрать несколько ответов
5 Страница содержит 2000 текстовых токенов. В режиме Small DeepSeek-OCR кодирует её в 100 визуальных токенов. Чего ждать по данным статьи?
6 Что показала статья «Optical Context Compression Is Just (Bad) Autoencoding»?
  • ViT превращает картинку в последовательность патчей; патч-эмбеддинг — это свёртка с ядром и шагом pp. Число визуальных токенов =(H/p)(W/p)= (H/p)(W/p) — главный параметр стоимости VLM.
  • CLIP учит два энкодера контрастивно: InfoNCE — это cross-entropy по строкам и столбцам матрицы сходств, а температура растягивает шкалу косинусов.
  • VLM = визуальный энкодер + проектор + LLM. Китайские Qwen-VL и InternVL сделали стандартом динамическое разрешение, M-RoPE и тайлинг.
  • DeepSeek-OCR показала, что страницу можно закодировать в ~10 раз меньшим числом визуальных токенов, чем текстовых, почти без потерь для OCR. Но вывод «картинки — лучший способ сжимать контекст» оспорен: громкие идеи требуют сильных базлайновбазовое решение, бейзлайнEN baselineПростейшая модель (среднее, самый частый класс), с которой сравнивают всё остальное. — простых методов для сравнения.

Дальше: диффузионные модели — как нейросеть рисует картинки и видео, начиная с чистого шума.