ReMe/tests/test_cache_memory_usage.py

209 lines
7.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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")