""" Calculate and measure the memory usage of embedding cache. This script estimates and measures the actual memory footprint of the embedding cache at different sizes. """ import sys from collections import OrderedDict def calculate_theoretical_memory(): """Calculate theoretical memory usage for embedding cache.""" print("=" * 70) print("Theoretical Memory Usage Calculation") print("=" * 70) # Constants dimensions = 1024 # Default embedding dimensions bytes_per_float = 8 # Python float is 64-bit cache_sizes = [1000, 5000, 10000, 50000, 100000] print("\nAssumptions:") print(f" • Embedding dimensions: {dimensions}") print(f" • Bytes per float: {bytes_per_float}") print(" • Hash key (SHA256 hex): 64 characters ≈ 128 bytes (UTF-8)") print(" • Python list overhead: ~56 bytes") print(" • Python string overhead: ~50 bytes") print(" • OrderedDict per-entry overhead: ~230 bytes") # Memory per single cache entry embedding_size = dimensions * bytes_per_float # Vector data list_overhead = 56 # Python list object overhead hash_key_size = 64 + 50 # String chars + string object overhead ordereddict_entry_overhead = 230 # Dict entry + ordering overhead entry_size = embedding_size + list_overhead + hash_key_size + ordereddict_entry_overhead print(f"\n{'─' * 70}") print("Memory per cache entry:") print(f" • Embedding vector: {embedding_size:,} bytes ({embedding_size/1024:.1f} KB)") print(f" • List overhead: {list_overhead} bytes") print(f" • Hash key: {hash_key_size} bytes") print(f" • OrderedDict overhead: {ordereddict_entry_overhead} bytes") print(f" • Total per entry: {entry_size:,} bytes ({entry_size/1024:.2f} KB)") print(f"\n{'─' * 70}") print("Memory usage at different cache sizes:") print(f"{'─' * 70}") print(f"{'Cache Size':>12} | {'Memory (MB)':>12} | {'Memory (GB)':>12}") print(f"{'─' * 70}") for size in cache_sizes: total_bytes = size * entry_size mb = total_bytes / (1024 * 1024) gb = total_bytes / (1024 * 1024 * 1024) print(f"{size:>12,} | {mb:>12.2f} | {gb:>12.4f}") print(f"{'─' * 70}") # Highlight default size default_size = 10000 default_memory_mb = (default_size * entry_size) / (1024 * 1024) print(f"\n✨ Default cache size (max_cache_size={default_size:,}):") print(f" Estimated memory: ~{default_memory_mb:.1f} MB") print("\n💡 Recommendation:") print(" • For memory-constrained environments: 1,000-5,000 (~8-42 MB)") print(" • For balanced performance: 10,000 (~84 MB) [DEFAULT]") print(" • For high-throughput applications: 50,000-100,000 (~420-840 MB)") print(" • To disable cache: max_cache_size=0") def measure_actual_memory(): """Measure actual memory usage with real data.""" print(f"\n\n{'=' * 70}") print("Actual Memory Measurement") print(f"{'=' * 70}") import random # Create a sample cache cache = OrderedDict() dimensions = 1024 test_sizes = [100, 1000, 5000, 10000] print("\nCreating embedding cache with random data...") print(f"{'─' * 70}") print(f"{'Cache Size':>12} | {'Memory (MB)':>12} | {'Per Entry (KB)':>15}") print(f"{'─' * 70}") for size in test_sizes: # Clear cache cache.clear() # Fill with sample data for i in range(size): hash_key = f"hash_{i:064x}" # 64-char hex string embedding = [random.random() for _ in range(dimensions)] cache[hash_key] = embedding # Measure memory (approximate) # Calculate size of all embeddings total_bytes = 0 for key, value in cache.items(): total_bytes += sys.getsizeof(key) # Key size total_bytes += sys.getsizeof(value) # List object total_bytes += len(value) * sys.getsizeof(float()) # Float elements # Add OrderedDict overhead total_bytes += sys.getsizeof(cache) mb = total_bytes / (1024 * 1024) per_entry_kb = total_bytes / size / 1024 print(f"{size:>12,} | {mb:>12.2f} | {per_entry_kb:>15.2f}") print(f"{'─' * 70}") # Measure default size cache.clear() default_size = 10000 print(f"\n📊 Measuring default size ({default_size:,} entries)...") for i in range(default_size): hash_key = f"hash_{i:064x}" embedding = [random.random() for _ in range(dimensions)] cache[hash_key] = embedding total_bytes = sys.getsizeof(cache) for key, value in cache.items(): total_bytes += sys.getsizeof(key) total_bytes += sys.getsizeof(value) total_bytes += len(value) * sys.getsizeof(float()) mb = total_bytes / (1024 * 1024) print(f" Actual memory usage: {mb:.2f} MB") print(f" Per entry: {total_bytes / default_size / 1024:.2f} KB") def print_usage_guidelines(): """Print guidelines for choosing cache size.""" print(f"\n\n{'=' * 70}") print("Cache Size Selection Guidelines") print(f"{'=' * 70}") print("\n📋 How to choose the right cache size:\n") print("1️⃣ Estimate your query patterns:") print(" • How many unique texts will you embed?") print(" • What's the repetition rate?") print(" • Example: 1,000 unique texts with 70% repetition → cache_size=1,000\n") print("2️⃣ Consider available memory:") print(" • ~84 MB per 10,000 entries (1024-dim embeddings)") print(" • ~420 MB per 50,000 entries") print(" • Scale linearly: ~8.4 MB per 1,000 entries\n") print("3️⃣ Monitor cache statistics:") print(" • Use model.get_cache_stats() to check hit rate") print(" • If hit rate < 50%, consider reducing cache size") print(" • If hit rate > 90%, you might benefit from larger cache\n") print("4️⃣ Configuration examples:\n") examples = [ ("Embedded device / IoT", "max_cache_size=100", "~840 KB"), ("Development / Testing", "max_cache_size=1000", "~8.4 MB"), ("Production (default)", "max_cache_size=10000", "~84 MB"), ("High-volume service", "max_cache_size=50000", "~420 MB"), ("Disable cache", "max_cache_size=0", "0 MB"), ] print(f"{'Use Case':<25} | {'Configuration':<22} | {'Memory':<10}") print(f"{'─' * 25}-+-{'─' * 22}-+-{'─' * 10}") for use_case, config, memory in examples: print(f"{use_case:<25} | {config:<22} | {memory:<10}") print("\n💡 Code example:\n") print(" # Default configuration (recommended)") print(" model = OpenAIEmbeddingModel(") print(" model_name='text-embedding-v4',") print(" dimensions=1024,") print(" max_cache_size=10000, # ~84 MB") print(" )") print() print(" # Memory-constrained configuration") print(" model = OpenAIEmbeddingModel(") print(" model_name='text-embedding-v4',") print(" dimensions=1024,") print(" max_cache_size=1000, # ~8.4 MB") print(" )") print() print(" # Monitor cache performance") print(" stats = model.get_cache_stats()") print(" print(f'Hit rate: {stats[\"hit_rate\"]:.1%}')") print(" print(f'Memory used: ~{stats[\"cache_size\"] * 8.4:.1f} KB')") if __name__ == "__main__": calculate_theoretical_memory() measure_actual_memory() print_usage_guidelines() print(f"\n{'=' * 70}") print("✅ Memory analysis complete") print(f"{'=' * 70}\n")