Cosine Similarity

2 posts

line4 min readCurated summary

On-Device Image Model

The post describes building an on-device image understanding system for messaging apps, with semantic image search as the first focus. Its central strategy was knowledge distillation: a multilingual student text encoder learned to reproduce the embedding space of a strong, English-only teacher model. This preserved most English retrieval quality while enabling Japanese, Traditional Chinese, Thai, and Korean search, achieving an average Recall@5 above 78%. ## Why Messaging Apps Need On-Device Image Understanding - Images are often treated simply as “a photo,” unlike text messages, which can support search, summaries, and notification previews. - Image understanding could improve: - Notifications: “Sent one photo” → “Sent a photo of a dog” - Search: queries such as “dog,” “puppy,” or “a cat inside a box” - Recommendations: automatic image classification and organization - Shared image-text embeddings allow semantically equivalent phrases—such as “dog,” “puppy,” and “개”—to retrieve the same images. ## Why the Model Had to Run On-Device - **Latency:** Network round trips make notifications and search less predictable and responsive. - **Privacy:** Sending photos, captions, or embeddings to a server increases privacy risks. - **Offline support:** The feature should work in subways, airplanes, roaming environments, and unstable networks. - **Mobile constraints:** The model needed to run on both Android and iOS with limited memory and compute resources. - The project targeted a model under **200 MB**, response times within a few hundred milliseconds including cold start, and LiteRT compatibility. ## Project Goals and Evaluation - The image search system needed to: - Retrieve images by semantic meaning rather than keyword matching. - Support English, Japanese, Traditional Chinese, Thai, and Korean. - A separate captioning system was designed to generate short, natural descriptions of roughly eight words or fewer. - Search quality was measured using: - Image-to-Text Recall@5 - Text-to-Image Recall@5 - Caption quality was evaluated with CIDEr, CLIPScore, and an LLM-based acceptance ratio designed to detect repetition, typos, and grammatical problems. ## Why Translation Was Not Enough The initial approach translated each query into English before using an English-only image-text model: ```text Query → Language detection → Translation → English text encoder → Embedding → Search ``` This approach introduced several problems: - **Quality loss:** Informal terms or short queries could be mistranslated. For example, “멍멍이” might be interpreted as “barking” instead of “dog.” - **Additional latency:** Translation adds a fixed cost before text encoding. - **Inconsistent results:** Translation quality varies by language pair and wording. - **Operational complexity:** Each additional language requires more models, updates, and failure handling. Training a multilingual image-text model from scratch would require substantial data and compute. Instead, the project retained the proven English image embedding space and expanded only the text encoder. ## Knowledge Distillation for Multilingual Search - The original English text encoder served as the frozen **teacher**. - A copied text encoder served as the trainable **student**. - English text was passed to the teacher, while corresponding multilingual text was passed to the student. - The student was trained to match the teacher’s embeddings using mean squared error (MSE). ```text teacher_embedding = teacher(English text) student_embedding = student(Multilingual text) loss = MSE(teacher_embedding, student_embedding) ``` The image encoder remained frozen so that the established image-text embedding space would not be disrupted. Important implementation considerations included: - Ensuring the tokenizer handled multilingual characters correctly. - Defining consistent case-insensitivity rules. - Balancing training samples across languages. - Matching training-time preprocessing and tokenization with mobile inference behavior. ## Retrieval Results - English performance declined slightly: - Image-to-Text Recall@5: **79.58% → 76.56%** - Text-to-Image Recall@5: **75.89% → 74.47%** - Multilingual performance improved from below **10% average Recall@5** to above **78%**, roughly a sevenfold improvement. - Japanese achieved **81.94%**, exceeding the original English model in the reported evaluation. - Traditional Chinese, Thai, and Korean also reached practically usable retrieval quality. The trade-off—slightly lower English performance in exchange for four additional languages—provided substantially greater overall product value. ## Converting the Model to LiteRT - LiteRT was selected because it officially supports both Android and iOS and provides mobile-oriented operators, quantization, and optimization tools. - Core ML was rejected because it is iOS-specific and introduced conversion and long-term cross-platform maintenance concerns. - Conversion required addressing unsupported PyTorch operators. - For example, LiteRT did not support `erf`, so the model’s implementation had to replace it with a compatible pseudo-`erf` operation. The resulting approach demonstrates that knowledge distillation can efficiently extend an existing English image-text model to multiple languages while preserving its on-device deployment advantages.

Read original(opens in new tab)
netflix3 min readCurated summary

Optimizing Recommendation Systems with JDK’s Vector API

Netflix’s Ranker service used significant CPU for video serendipity scoring, which compares candidate-title embeddings with a member’s viewing history. The team reduced CPU usage by progressively replacing scalar dot products with batched computation, improving memory layout, reusing buffers, and investigating optimized matrix-multiplication libraries. The main lesson was that mathematical optimization alone is insufficient; allocation behavior, cache locality, SIMD support, and runtime overhead all matter. ## The Serendipity Scoring Hotspot - Each candidate title and history item is represented by a vector embedding. - The service computes cosine similarity between every candidate and every history item. - It selects the maximum similarity and converts it into a novelty score: - `serendipity = 1.0 - maxSimilarity` - The original implementation performed `M × N` individual dot products, creating: - Sequential computational work - Repeated embedding lookups - Scattered memory access - Poor cache locality - This logic consumed roughly 7.5% of CPU per Ranker node. - Although 98% of requests contained one video, large batch requests represented about half of the total videos processed. ## Batching Similarity Computations - The team reorganized the calculation as matrix multiplication: - Candidate embeddings form an `M × D` matrix. - History embeddings form an `N × D` matrix. - Rows are normalized to unit length. - Similarities are computed as `C = A × Bᵀ`. - This replaces many separate dot products with one larger operation better suited to CPU-optimized kernels. - The implementation added `batchEncode()` while preserving the existing `encode()` path for single-video requests. ## Why the First Batched Version Regressed - Initial canary tests showed a 5% performance regression. - The batched implementation created `double[][]` arrays for candidates, history, and results on every request. - These allocations: - Increased garbage-collection pressure - Used non-contiguous memory - Added pointer chasing and reduced cache efficiency - The matrix multiplication itself was scalar Java code and did not exploit SIMD hardware. - Batching therefore introduced overhead without delivering corresponding compute gains. ## Flat Buffers and Thread-Local Reuse - The team replaced multidimensional arrays with flat `double[]` buffers in row-major order. - Contiguous storage improved predictability and cache locality. - A `ThreadLocal<BufferHolder>` was used to retain reusable candidate, history, and scratch buffers per thread. - Buffers grow when necessary but do not shrink, avoiding repeated allocations while preventing cross-thread contention. - This reduced GC pressure and made batch performance more stable. ## Evaluating BLAS - BLAS appeared promising in isolated microbenchmarks but did not provide the expected production improvement. - The default `netlib-java` configuration used F2J, a Java implementation rather than truly native BLAS. - Native BLAS introduced setup costs and JNI transition overhead. - Java’s row-major data layout also created an impedance mismatch with common BLAS expectations.

Read original(opens in new tab)