Adaptable Dimension Embeddings: A Technical Guide for AI Engineers and Software Developers
Apr 13, 2025
20 min read
Saumil Srivastava
Engineering Leader
Table Of Contents
Loading content outline...
Introduction: Why Dimension Size Matters in AI Systems
As a software engineer or ML practitioner building AI-powered applications, you've likely encountered the fundamental role of embeddings—numerical vector representations that encode semantic information about text, images, or other data types. These embeddings power everything from similarity search to classification and recommendation systems.
However, you've probably also faced a critical engineering challenge: embedding dimensions create unavoidable trade-offs in your AI systems:
Adaptable Dimension Embeddings: A Technical Guide for AI Engineers and Software Developers | Saumil Srivastava's Blog
This technical dilemma traditionally forced engineers to choose a fixed dimension size and accept the associated limitations. Adaptive dimension embeddings, enabled by techniques like Matryoshka Representation Learning (MRL), provide a powerful solution that lets you dynamically adjust this trade-off based on your application's needs.
This guide will walk you through:
The technical foundations of adaptive dimension embeddings
How to implement and utilize them in production systems
Performance benchmarking and optimization techniques
Practical coding patterns for different use cases
Implementing Adaptable Dimension Embeddings in Your Code
Using OpenAI's Embedding Models with Variable Dimensions
OpenAI's `text-embedding-3-small` and `text-embedding-3-large` models directly support variable dimensions:
1from openai import OpenAI
23defget_embedding(text, dimension=1536):4"""Get embedding with specified dimension"""5 client = OpenAI()67 response = client.embeddings.create(8 model="text-embedding-3-small",9input=text,10 dimensions=dimension # Request specific dimension11)1213return response.data[0].embedding
1415# Example usage16query_embedding_full = get_embedding("What is machine learning?", dimension=1536)17query_embedding_reduced = get_embedding("What is machine learning?", dimension=256)1819# The reduced embedding retains most semantic information while being ~6x smaller20
Using Open-Source MRL Models
Several open-source models also support variable dimensions, such as Nomic AI's models:
1from nomic import embed
2import numpy as np
34defget_nomic_embedding(texts, dimension=768):5"""Get Nomic embeddings with specified dimension"""6 output = embed.text(7 texts=texts,8 model='nomic-embed-text-v1.5',9 task_type="search_document",10 dimensionality=dimension # Request specific dimension11)1213return np.array(output['embeddings'])1415# Example usage16docs =["Machine learning is a field of AI","Neural networks are popular in ML"]17doc_embeddings = get_nomic_embedding(docs, dimension=256)18
Implementation Tutorial: Building MRL From Scratch
Let's implement a complete MRL system using PyTorch. We'll build a text classification system using the 20 Newsgroups dataset that generates embeddings at multiple dimensions (32, 64, 128, 256, 512, 768).
Step 1: Setting Up the Environment
First, let's set up our imports and prepare the dataset:
1import numpy as np
2import matplotlib.pyplot as plt
3from sklearn.datasets import fetch_20newsgroups
4from sklearn.feature_extraction.text import TfidfVectorizer
5from sklearn.metrics import accuracy_score
6from sklearn.model_selection import train_test_split
7import time
8import torch
9import torch.nn as nn
10import torch.optim as optim
11from torch.utils.data import DataLoader, TensorDataset
1213# Set random seeds for reproducibility14np.random.seed(42)15torch.manual_seed(42)1617# Load data18categories =['alt.atheism','comp.graphics','sci.med','soc.religion.christian']19newsgroups = fetch_20newsgroups(subset='all', categories=categories,20 remove=('headers','footers','quotes'))21X, y = newsgroups.data, newsgroups.target
2223# Create train/test split24X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)2526# Create initial features using TF-IDF27vectorizer = TfidfVectorizer(max_features=2000)28X_train_tfidf = vectorizer.fit_transform(X_train)29X_test_tfidf = vectorizer.transform(X_test)3031# Convert to PyTorch tensors32X_train_tensor = torch.FloatTensor(X_train_tfidf.toarray())33X_test_tensor = torch.FloatTensor(X_test_tfidf.toarray())34y_train_tensor = torch.LongTensor(y_train)35y_test_tensor = torch.LongTensor(y_test)36
Step 2: Designing the MRL Encoder Architecture
Now, let's create the MRL encoder that produces hierarchically structured embeddings:
1classMRLEncoder(nn.Module):2def__init__(self, input_dim, max_dim=768):3super(MRLEncoder, self).__init__()4 self.max_dim = max_dim
56# Encoder network - this produces the full-dimensional embedding7 self.encoder = nn.Sequential(8 nn.Linear(input_dim,1024),9 nn.ReLU(),10 nn.Linear(1024, max_dim)11)1213# Projection layers for different dimensions14# These ensure the embeddings have the nested Matryoshka structure15 self.projections = nn.ModuleDict()16 self.dims =[32,64,128,256,512, max_dim]1718for dim in self.dims:19 self.projections[str(dim)]= nn.Linear(max_dim, dim, bias=False)2021# Critical step: Initialize projections to ensure nested structure22# This makes smaller embeddings subsets of larger ones23 self._initialize_nested_projections()2425def_initialize_nested_projections(self):26"""Initialize projection matrices to enforce nested structure"""27with torch.no_grad():28for i, dim1 inenumerate(self.dims[:-1]):29 dim2 = self.dims[i+1]30# Initialize so that projection to dim1 is equivalent to:31# 1. Projecting to dim232# 2. Then taking the first dim1 dimensions33 self.projections[str(dim1)].weight.data = \
34 self.projections[str(dim2)].weight.data[:dim1,:]3536defforward(self, x, dim=None):37"""
38 Forward pass that can output embeddings at any requested dimension
3940 Args:
41 x: Input tensor
42 dim: Target dimension (if None, returns max dimension)
4344 Returns:
45 Embedding tensor at requested dimension
46 """47# Get the full-dimensional embedding from the encoder48 full_emb = self.encoder(x)4950# If no dimension specified or max dimension requested, return full embedding51if dim isNoneor dim == self.max_dim:52if dim == self.max_dim andstr(dim)in self.projections:53return self.projections[str(dim)](full_emb)54return full_emb
5556# Use the appropriate projection for the requested dimension57ifstr(dim)in self.projections:58return self.projections[str(dim)](full_emb)59else:60# For dimensions not explicitly defined, find the nearest smaller one61 available_dims =sorted([int(d)for d in self.projections.keys()])62 nearest =max([d for d in available_dims if d <= dim])63return self.projections[str(nearest)](full_emb)[:,:dim]64
Step 3: Building the Classification Head
Next, we'll create a classifier that works with multiple embedding dimensions:
1classMRLClassifier(nn.Module):2def__init__(self, input_dim, num_classes, max_dim=768):3super(MRLClassifier, self).__init__()4 self.encoder = MRLEncoder(input_dim, max_dim)56# Create separate classification heads for each dimension7# This is important for optimizing classification at each dimension level8 self.dims =[32,64,128,256,512, max_dim]9 self.classifiers = nn.ModuleDict()1011for dim in self.dims:12 self.classifiers[str(dim)]= nn.Linear(dim, num_classes)1314defforward(self, x, dim=None):15"""
16 Forward pass with dimension-specific classification
1718 Args:
19 x: Input tensor
20 dim: Target dimension (if None, uses max dimension)
2122 Returns:
23 Classification logits
24 """25# Get embeddings at the specified dimension26 emb = self.encoder(x, dim)2728# Default to max dimension if none specified29if dim isNone:30 dim = self.encoder.max_dim
3132# Use the appropriate classifier for this dimension33ifstr(dim)in self.classifiers:34return self.classifiers[str(dim)](emb)35else:36# For non-standard dimensions, use the nearest smaller classifier37 available_dims =sorted([int(d)for d in self.classifiers.keys()])38 nearest =max([d for d in available_dims if d <= dim])39return self.classifiers[str(nearest)](emb[:,:nearest])40
Step 4: Implementing the Multi-Dimension Training Loop
This is where MRL differs significantly from standard training. We need to train across multiple dimensions simultaneously:
1# Initialize the model2input_dim = X_train_tfidf.shape[1]# TF-IDF feature dimension3num_classes =len(np.unique(y))# Number of classes in dataset4model = MRLClassifier(input_dim, num_classes)56# Training parameters7batch_size =648train_dataset = TensorDataset(X_train_tensor, y_train_tensor)9train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)10criterion = nn.CrossEntropyLoss()11optimizer = optim.Adam(model.parameters(), lr=0.001)1213# Training loop14num_epochs =515dims_to_train =[32,64,128,256,512,768]# Train all dimensions1617for epoch inrange(num_epochs):18 model.train()19 total_loss =02021for batch_X, batch_y in train_loader:22# Critical: Train each dimension in each batch23# This ensures all dimensions learn effectively24for dim in dims_to_train:25# Forward pass at this dimension26 optimizer.zero_grad()27 outputs = model(batch_X, dim)2829# Calculate loss and backpropagate30 loss = criterion(outputs, batch_y)31 loss.backward()32 optimizer.step()3334 total_loss += loss.item()3536# Print progress37 avg_loss = total_loss /(len(train_loader)*len(dims_to_train))38print(f"Epoch {epoch+1}/{num_epochs}, Avg Loss: {avg_loss:.4f}")39
Step 5: Evaluation and Performance Analysis
Now let's evaluate our model across different dimensions to see the accuracy-efficiency tradeoff:
1# Evaluate performance at each dimension2model.eval()3results =[]45for dim in dims_to_train:6 start_time = time.time()78with torch.no_grad():9 outputs = model(X_test_tensor, dim)10 _, predicted = torch.max(outputs,1)1112# Calculate inference time13 inference_time = time.time()- start_time
1415# Calculate accuracy16 accuracy = accuracy_score(y_test, predicted.numpy())1718# Calculate storage requirements (in KB)19# Assuming 4 bytes per float (32-bit floating point)20 storage_kb =(dim *4)/10242122# Store results23 results.append({24'dimension': dim,25'accuracy': accuracy,26'inference_time': inference_time,27'storage_kb': storage_kb
28})2930print(f"Dimension: {dim}, Accuracy: {accuracy:.4f}, "31f"Inference Time: {inference_time:.4f}s, Storage: {storage_kb:.2f}KB")3233# Create a performance comparison table34from tabulate import tabulate
35table_data =[[r['dimension'],f"{r['accuracy']:.4f}",36f"{r['inference_time']*1000:.2f}ms",f"{r['storage_kb']:.2f}KB"]37for r in results]38headers =["Dimension","Accuracy","Inference Time","Storage"]39print(tabulate(table_data, headers, tablefmt="grid"))40
Step 6: Visualization and Analysis
Let's visualize our results to understand the performance-efficiency tradeoff:
1# Visualization of results2plt.figure(figsize=(15,5))34# Plot accuracy vs dimension5plt.subplot(1,3,1)6plt.plot([r['dimension']for r in results],[r['accuracy']for r in results],'o-')7plt.xlabel('Embedding Dimension')8plt.ylabel('Accuracy')9plt.title('Accuracy vs Embedding Dimension')10plt.grid(True)1112# Plot storage vs dimension13plt.subplot(1,3,2)14plt.plot([r['dimension']for r in results],[r['storage_kb']for r in results],'o-')15plt.xlabel('Embedding Dimension')16plt.ylabel('Storage (KB)')17plt.title('Storage Requirements vs Dimension')18plt.grid(True)1920# Plot accuracy vs storage21plt.subplot(1,3,3)22plt.plot([r['storage_kb']for r in results],[r['accuracy']for r in results],'o-')23plt.xlabel('Storage (KB)')24plt.ylabel('Accuracy')25plt.title('Accuracy vs Storage Trade-off')26plt.grid(True)2728plt.tight_layout()29plt.savefig('mrl_performance_tradeoff.png')30plt.show()31
Step 7: Bonus - Visualizing Embedding Structure with t-SNE
To understand how well our embeddings preserve class information across dimensions:
1from sklearn.manifold import TSNE
23# Get embeddings at different dimensions4all_embeddings ={}5with torch.no_grad():6for dim in[32,768]:# Compare smallest and largest7 embeddings = model.encoder(X_test_tensor, dim).numpy()8 all_embeddings[dim]= embeddings
910# Apply t-SNE to reduce to 2D for visualization11tsne_results ={}12for dim, emb in all_embeddings.items():13# For speed, use a sample of the test set14 sample_size =min(500,len(emb))15 sample_indices = np.random.choice(len(emb), sample_size, replace=False)16 sample_embeddings = emb[sample_indices]17 sample_labels = y_test[sample_indices]1819# Apply t-SNE20 tsne = TSNE(n_components=2, random_state=42)21 tsne_result = tsne.fit_transform(sample_embeddings)22 tsne_results[dim]=(tsne_result, sample_labels)2324# Plot t-SNE visualizations25plt.figure(figsize=(12,5))26for i, dim inenumerate([32,768]):27 plt.subplot(1,2, i+1)28 tsne_result, labels = tsne_results[dim]2930# Plot each class with a different color31for class_idx inrange(num_classes):32 mask = labels == class_idx
33 plt.scatter(tsne_result[mask,0], tsne_result[mask,1],34 label=f'Class {class_idx}')3536 plt.title(f't-SNE Visualization of {dim}-D Embeddings')37 plt.legend()3839plt.tight_layout()40plt.savefig('mrl_tsne_visualization.png')41plt.show()42
Implementing Adaptive Retrieval for Optimized Search
One of the most powerful applications of adaptable dimension embeddings is a technique called "Adaptive Retrieval," which dramatically improves search performance.
The Two-Pass Approach
Adaptive Retrieval uses a two-stage process to balance speed and accuracy:
First pass: Use low-dimensional embeddings (e.g., 256d) to quickly find potential matches
Second pass: Re-rank the top candidates using high-dimensional embeddings (e.g., 1536d)
This approach can achieve up to 10-14x speed improvements with negligible accuracy loss.
Implementation Example
1defadaptive_retrieval(query, vector_db, embedding_model, low_dim=256, high_dim=1536):2"""
3 Perform two-stage retrieval using adaptive dimensions
45 Args:
6 query: The search query text
7 vector_db: Vector database instance
8 embedding_model: Model that supports variable dimensions
9 low_dim: Dimension for initial fast search
10 high_dim: Dimension for accurate re-ranking
1112 Returns:
13 List of ranked results
14 """15# Generate low-dimensional query embedding for fast initial search16 query_embedding_low = embedding_model.embed(query, dimension=low_dim)1718# First pass: Retrieve candidate set using fast low-dim search19# Typically retrieve more results than needed (e.g., 100-200)20 candidates = vector_db.search(21 vector=query_embedding_low,22 dimension=low_dim,23 limit=20024)2526# Extract candidate IDs27 candidate_ids =[item['id']for item in candidates]2829# Get high-dimensional embeddings for candidates30 candidate_vectors = vector_db.get_vectors_by_ids(31 ids=candidate_ids,32 dimension=high_dim
33)3435# Generate high-dimensional query embedding for accurate scoring36 query_embedding_high = embedding_model.embed(query, dimension=high_dim)3738# Second pass: Re-rank candidates using high-dimensional similarity39 results =[]40for doc_id, vector in candidate_vectors.items():41# Calculate similarity (e.g., cosine similarity)42 similarity = cosine_similarity(query_embedding_high, vector)43 results.append({44'id': doc_id,45'score': similarity
46})4748# Sort by similarity score (descending)49 results.sort(key=lambda x: x['score'], reverse=True)5051# Return top results (e.g., top 10)52return results[:10]53
Vector Database Considerations
Implementing adaptive retrieval requires a vector database that supports:
Indexing and searching at different dimensions: The database must efficiently search embeddings at the lower dimension
Efficient batch retrieval by ID: For quickly retrieving full-dimensional vectors of candidates
Optional but helpful - Hybrid indexes: Some databases support creating special indexes that store multiple dimension versions
Production Implementation Patterns
When deploying adaptable dimension embeddings in production, consider these architectural patterns:
1. Embedding Pipeline with Dimension Branching
Input Text → Embedding Generation → Store Multiple Dimensions
├→ Full Dim (DB #1)
├→ Mid Dim (DB #2)
└→ Low Dim (DB #3)
2. Single Database with Multi-Dimensional Indexing
Initial Request → Low-Dim Results (fast) →
Background Load → Mid-Dim Results →
User Interaction → High-Dim Final Results
Conclusion: Best Practices for Engineers
As you implement adaptable dimension embeddings in your ML systems, keep these best practices in mind:
Benchmark extensively: Don't assume theoretical performance gains; measure the accuracy-speed-storage tradeoffs for your specific use case.
Start with the simplest approach: Begin with basic dimension truncation before implementing complex adaptive retrieval.
Monitor key metrics: Track embedding generation time, query latency, storage usage, and accuracy metrics across different dimensions.
Consider your infrastructure: Ensure your vector database supports efficient operations at different dimensions.
Build progressive systems: Design your architecture to start with fast, low-dimensional processing and progressively enhance with higher dimensions where needed.
Mastering adaptable dimension embeddings gives you a powerful tool for optimizing AI systems, allowing you to find the perfect balance between performance, cost, and accuracy for your specific application requirements.
By implementing the techniques in this guide, you'll be well-equipped to build more efficient and scalable AI systems that deliver better user experiences while controlling infrastructure costs.
---
Want to see practical implementations of these concepts? After reading this guide, check out our reference implementation on GitHub for complete code examples, benchmarking tools, and additional resources to help you implement adaptable dimension embeddings in your own projects.
---
Looking to level up your AI engineering skills? Join our community of engineers implementing cutting-edge techniques in production. Sign up for our newsletter for weekly technical deep dives and code examples.
Subscribe to the Newsletter
Get weekly insights on AI implementation, performance measurement, and technical case studies.
Join the Newsletter
Get weekly insights on AI implementation and technical case studies.
Learn how to leverage adaptable dimension embeddings techniques like Matryoshka Representation Learning enables engineering leaders to optimize AI embedding models, reducing storage costs by up to 24x while maintaining 99.7% performance accuracy.