Как работает семантический поиск изображений на устройстве?
Представьте: пользователь вводит «собака на пляже», и приложение мгновенно выводит все фото собаки на пляже — без тегов, без ручной сортировки. Это семантический поиск изображений на основе CLIP (Contrastive Language-Image Pre-training) от OpenAI. Мы реализуем его полностью на устройстве: изображения и тексты переводятся в 512-мерные векторы, а косинусное сходство между ними определяет релевантность. Никакой отправки данных на сервер — приватность пользователя сохраняется. Наш опыт с MobileCLIP-S0 и векторными БД позволяет развернуть такое решение за 1–4 недели.
Оцените возможности для вашего приложения — напишите нам, обсудим детали. Экономия по сравнению с облачным поиском достигает 80% за счёт отсутствия платы за инференс. On-device CLIP поиск в 5 раз быстрее облачного благодаря локальному инференсу. Окупаемость инвестиций в on-device AI составляет обычно 6–12 месяцев. Получите консультацию — свяжитесь с нами.
Архитектура и преимущества on-device AI
Пайплайн состоит из двух независимых этапов:
- Индексация (один раз для всей галереи, потом инкрементально): для каждого фото вычисляем CLIP Image Embedding (512-мерный вектор) и сохраняем в локальную векторную БД.
- Поиск (при каждом запросе пользователя): запрос пользователя превращается в CLIP Text Embedding (тот же 512-мерный вектор), выполняется ANN-поиск ближайших векторов в базе, возвращаются фото по убыванию косинусного сходства.
| Сравнение | On-device | Cloud |
|---|---|---|
| Латенти | 15-20 ms | 200-500 ms |
| Стоимость за 10k запросов | бесплатно | существенные затраты |
| Приватность | Данные на устройстве | Данные на сервере |
On-device AI выгоднее облачного: отсутствие задержки сети, нулевая стоимость инференса, полная приватность пользователя. Экономия до 80% по сравнению с облачными решениями, быстрая окупаемость.
Интеграция CLIP через CoreML
Apple не включила CLIP в стандартный Vision framework, но Apple ML Research выпустила ml-mobileclip — дистиллированную версию специально для мобильных устройств. MobileCLIP-S0: 18 MB, 3–5 ms на инференс изображения на iPhone 14.
MobileCLIP — официальный репозиторий с кодом.
import CoreML
class MobileCLIPEmbedder {
private let imageEncoder: MobileCLIPImageEncoder
private let textEncoder: MobileCLIPTextEncoder
func embedImage(_ cgImage: CGImage) throws -> [Float] {
let resized = resize(cgImage, to: CGSize(width: 256, height: 256))
let input = MobileCLIPImageInput(image: MLMultiArray(from: resized))
let output = try imageEncoder.prediction(input: input)
return l2Normalize(output.embedding.toFloatArray())
}
func embedText(_ query: String) throws -> [Float] {
let tokens = tokenize(query) // BPE tokenizer
let input = MobileCLIPTextInput(tokens: MLMultiArray(from: tokens))
let output = try textEncoder.prediction(input: input)
return l2Normalize(output.embedding.toFloatArray())
}
}
Tokenizer для CLIP — BPE (Byte Pair Encoding). Swift-реализация есть в репозитории apple/ml-mobileclip. На Android: ONNX Runtime с MobileCLIP — менее удобно, но работает.
Как интегрировать CLIP в iOS приложение?
Пошаговая инструкция для внедрения семантического поиска:
- Подготовить модель. Скачайте MobileCLIP-S0 из репозитория Apple ML. Конвертируйте в CoreML с помощью
coremltools. - Интегрировать энкодеры. Создайте классы для обработки изображений и текста, как в примере выше. Убедитесь, что нормализация и токенизация соответствуют.
- Настроить индексацию. Используйте
BGProcessingTaskдля фоновой индексации галереи. Сохраняйте эмбеддинги в векторную БД (например, sqlite-vss). - Реализовать поиск. При получении запроса вычисляйте эмбеддинг текста и выполняйте ANN-поиск. Возвращайте отсортированные результаты.
Пример фоновой индексации:
class GalleryIndexer {
private var lastIndexedDate: Date {
get { UserDefaults.standard.object(forKey: "lastIndexedDate") as? Date ?? .distantPast }
set { UserDefaults.standard.set(newValue, forKey: "lastIndexedDate") }
}
func indexNewPhotos() async {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "creationDate > %@", lastIndexedDate as CVarArg)
let newPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
newPhotos.enumerateObjects { [weak self] asset, _, _ in
guard let self else { return }
if let embedding = self.computeEmbedding(for: asset) {
self.vectorDB.insert(assetId: asset.localIdentifier, embedding: embedding)
}
}
lastIndexedDate = Date()
}
}
Поиск выполняется за ~20 ms: text embedding (5 ms) + ANN search (15 ms). Результаты мгновенные.
func search(query: String, topK: Int = 30) async throws -> [PHAsset] {
let textEmbedding = try mobileCLIP.embedText(query)
let results = vectorDB.search(vector: textEmbedding, limit: topK)
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(
format: "localIdentifier IN %@",
results.map { $0.assetId }
)
let assets = PHAsset.fetchAssets(with: fetchOptions)
let idToScore = Dictionary(uniqueKeysWithValues: results.map { ($0.assetId, $0.score) })
return assets.objects(at: IndexSet(0..<assets.count))
.sorted { idToScore[$0.localIdentifier, default: 0] > idToScore[$1.localIdentifier, default: 0] }
}
Как мы выбираем векторную БД на устройстве?
Для поиска среди 50 000 векторов нужен ANN-индекс. Рассмотрим три варианта с конкретными характеристиками:
| Технология | Производительность | Сложность интеграции | Объём базы |
|---|---|---|---|
SQLite + sqlite-vss |
15-20 ms на поиск | Средняя (SQL-расширение) | 10k-50k |
| FAISS (C++ через JNI/Swift) | 5-10 ms на поиск | Высокая (сборка под платформу) | 50k-500k |
| Flat L2 через Accelerate | 15 ms на 10k векторов | Низкая (стандартная библиотека) | до 10k |
SQLite с расширением sqlite-vss — добавляет виртуальные таблицы для векторного поиска. Компактный, работает в embedded режиме:
CREATE VIRTUAL TABLE photo_embeddings USING vss0(embedding(512));
INSERT INTO photo_embeddings(rowid, embedding) VALUES (42, json('[0.1, -0.3, ...]'));
SELECT rowid, distance FROM photo_embeddings WHERE vss_search(embedding, json('[0.2, -0.1, ...]')) LIMIT 20;
Простой flat L2/cosine через Accelerate — для галерей до 10k фото вполне достаточно без специализированного индекса:
func cosineSimilarity(_ a: [Float], _ b: [Float]) -> Float {
var dotProduct: Float = 0
vDSP_dotpr(a, 1, b, 1, &dotProduct, vDSP_Length(a.count))
return dotProduct // После L2-нормализации = косинусное сходство
}
Перебор 10000 512-мерных векторов на iPhone 14 через vDSP_dotpr — ~15 ms. Для галерей до 20k приемлемо.
Мультиязычный поиск
CLIP обучен преимущественно на английском. Для русского запроса «собака на пляже» — качество хуже, чем для «dog on beach». Решение: перевод запроса через простой словарь частых слов или Google Translate API перед embeddings. На практике достаточно перевести 100–200 частых запросов без API.
Что входит в работу и сколько это займет?
| Задача | Сроки |
|---|---|
| Базовый CLIP-поиск с flat index для галерей до 10k | 1–1.5 недели |
| Масштабируемая реализация с ANN-индексом, инкрементальным обновлением, мультиязычностью и визуальным поиском по референс-фото | 3–4 недели |
Стоимость рассчитывается индивидуально в зависимости от сложности интеграции и целевых устройств. Получите консультацию — свяжитесь с нами для точной оценки.
Почему стоит доверить эту задачу нам?
Мы специализируемся на мобильном ML более 6 лет и реализовали 15+ проектов с on-device AI. Гарантируем соблюдение App Store Review Guidelines (разделы 4.2 и 5.1) и безопасность пользовательских данных. Предоставляем полную документацию и поддержку после внедрения.







