Skip to content

Latest commit

 

History

History
352 lines (281 loc) · 8.98 KB

File metadata and controls

352 lines (281 loc) · 8.98 KB

MemoryOS-Rust 算法优化分析

🎯 高价值优化点

1. 向量检索优化 ⭐⭐⭐⭐⭐

当前问题:

// 每次查询都要检索 Qdrant
async fn retrieve_memory(user_id: &str, query: &str) {
    // 1. 生成 embedding (1 次 LLM 调用)
    let embedding = generate_embedding(query).await;
    
    // 2. 检索短期记忆 (1 次 Redis 查询)
    let stm = redis.get_short_term(user_id).await;
    
    // 3. 检索中期记忆 (1 次 Qdrant 查询)
    let mtm = qdrant.search(embedding, limit=10).await;
    
    // 4. 检索长期记忆 (1 次 Qdrant 查询)
    let ltm = qdrant.search_profile(user_id).await;
    
    // 总计: 1 LLM + 1 Redis + 2 Qdrant = 4 次请求
}

优化方案 1: 批量检索

// 合并 Qdrant 查询
async fn retrieve_memory_optimized(user_id: &str, query: &str) {
    let embedding = generate_embedding(query).await;
    
    // 并行执行
    let (stm, memories) = tokio::join!(
        redis.get_short_term(user_id),
        qdrant.search_with_filter(
            embedding,
            filter = {
                "should": [
                    {"key": "type", "match": {"value": "mtm"}},
                    {"key": "type", "match": {"value": "ltm"}},
                    {"key": "user_id", "match": {"value": user_id}}
                ]
            },
            limit = 20
        )
    );
    
    // 总计: 1 LLM + 1 Redis + 1 Qdrant = 3 次请求
    // 减少 25% 请求!
}

优化方案 2: 缓存 Embedding

// 缓存常见查询的 embedding
pub struct EmbeddingCache {
    cache: Arc<RwLock<LruCache<String, Vec<f32>>>>,
}

async fn retrieve_with_cache(query: &str) {
    // 检查缓存
    if let Some(embedding) = cache.get(query) {
        // 跳过 LLM 调用!
        return qdrant.search(embedding).await;
    }
    
    // 缓存未命中才调用 LLM
    let embedding = generate_embedding(query).await;
    cache.put(query.to_string(), embedding.clone());
    
    // 对于高频查询,节省 100% LLM 调用!
}

2. FAQ 直接命中优化 ⭐⭐⭐⭐⭐

当前问题:

// 每次都要检索 + LLM
async fn chat(query: &str) {
    // 1. 检索记忆 (3-4 次请求)
    let context = retrieve_memory(query).await;
    
    // 2. 调用 LLM (1 次,耗时 1-3 秒)
    let response = llm.complete(query, context).await;
    
    // 总耗时: ~2 秒
}

优化方案: Bloom Filter + 精确匹配

pub struct FaqMatcher {
    // Bloom Filter 快速判断是否可能是 FAQ
    bloom: BloomFilter,
    // 精确匹配的 FAQ 缓存
    exact_cache: HashMap<u64, String>,
}

async fn chat_optimized(query: &str) {
    // 1. 计算查询哈希 (< 1μs)
    let hash = hash_query(query);
    
    // 2. Bloom Filter 检查 (< 1μs)
    if !bloom.contains(&hash) {
        // 肯定不是 FAQ,走正常流程
        return normal_chat(query).await;
    }
    
    // 3. 精确匹配检查 (< 1μs)
    if let Some(answer) = exact_cache.get(&hash) {
        // 直接返回!跳过所有检索和 LLM
        return answer.clone();
    }
    
    // 4. 向量检索确认 (仅对可能的 FAQ)
    let similar = qdrant.search(query, filter={"type": "faq"}, limit=1).await;
    if similar.score > 0.98 {
        // 缓存并返回
        exact_cache.insert(hash, similar.content.clone());
        return similar.content;
    }
    
    // 5. 不是 FAQ,走正常流程
    normal_chat(query).await
}

// 性能提升:
// - FAQ 命中: 0 次请求,< 1ms (vs 原来 4 次请求 + LLM,2 秒)
// - 非 FAQ: 1 次 Qdrant (vs 原来 4 次)

3. 批量 Embedding 生成 ⭐⭐⭐⭐

当前问题:

// 逐个生成 embedding
for message in messages {
    let embedding = llm.embed(message.content).await;  // N 次调用
    segments.push(MidTermSegment { embedding, .. });
}
// 10 条消息 = 10 次 API 调用

优化方案: 批量 API

// 一次调用生成多个 embedding
let contents: Vec<String> = messages.iter()
    .map(|m| m.content.clone())
    .collect();

let embeddings = llm.embed_batch(contents).await;  // 1 次调用!

for (message, embedding) in messages.iter().zip(embeddings) {
    segments.push(MidTermSegment { embedding, .. });
}

// 10 条消息 = 1 次 API 调用
// 减少 90% 请求!

4. 热度计算优化 ⭐⭐⭐

当前问题:

// 每次访问都更新 Qdrant
async fn record_access(segment_id: Uuid) {
    // 1. 读取 (1 次 Qdrant)
    let mut segment = qdrant.get(segment_id).await;
    
    // 2. 更新
    segment.access_count += 1;
    segment.heat_score = calculate_heat(&segment);
    
    // 3. 写回 (1 次 Qdrant)
    qdrant.update(segment).await;
}

// 100 次访问 = 200 次 Qdrant 请求

优化方案: 批量更新 + Redis 缓冲

pub struct HeatBuffer {
    // Redis 缓冲访问计数
    buffer: Arc<RwLock<HashMap<Uuid, u32>>>,
}

async fn record_access_optimized(segment_id: Uuid) {
    // 1. 只更新 Redis 计数器 (< 1ms)
    redis.incr(format!("heat:{}", segment_id)).await;
    
    // 不立即写 Qdrant!
}

// 后台任务:每 5 分钟批量同步
async fn sync_heat_scores() {
    loop {
        tokio::time::sleep(Duration::from_secs(300)).await;
        
        // 1. 从 Redis 读取所有计数 (1 次)
        let counts = redis.hgetall("heat:*").await;
        
        // 2. 批量读取 Qdrant (1 次)
        let segments = qdrant.get_batch(counts.keys()).await;
        
        // 3. 批量更新 (1 次)
        let updates = segments.iter().map(|s| {
            s.access_count += counts[&s.id];
            s.heat_score = calculate_heat(s);
            s
        }).collect();
        qdrant.update_batch(updates).await;
        
        // 4. 清空 Redis 缓冲
        redis.del("heat:*").await;
    }
}

// 100 次访问 = 100 次 Redis (快) + 3 次 Qdrant (批量)
// vs 原来 200 次 Qdrant
// 减少 98.5% Qdrant 请求!

5. 相似度计算优化 ⭐⭐⭐⭐

当前问题:

// 每次都计算完整的向量相似度
fn find_similar(query_embedding: &[f32], segments: &[Segment]) -> Vec<Segment> {
    segments.iter()
        .map(|s| {
            let score = cosine_similarity(query_embedding, &s.embedding);
            (s, score)
        })
        .filter(|(_, score)| *score > 0.7)
        .collect()
}
// 1000 个 segment = 1000 次完整计算

优化方案: 分层过滤

fn find_similar_optimized(query: &[f32], segments: &[Segment]) -> Vec<Segment> {
    // 1. 快速过滤:只比较前 64 维 (< 10% 计算量)
    let candidates: Vec<_> = segments.iter()
        .filter(|s| {
            let quick_score = dot_product(&query[..64], &s.embedding[..64]);
            quick_score > 0.5  // 粗筛
        })
        .collect();
    
    // 2. 精确计算:只对候选计算完整相似度
    candidates.iter()
        .map(|s| {
            let score = cosine_similarity(query, &s.embedding);
            (s, score)
        })
        .filter(|(_, score)| *score > 0.7)
        .collect()
}

// 假设粗筛过滤掉 80%
// 1000 个 segment = 1000 次快速计算 + 200 次完整计算
// vs 原来 1000 次完整计算
// 减少 ~70% 计算量!

6. 记忆合并优化 ⭐⭐⭐

当前问题:

// 每次都调用 LLM 总结
async fn consolidate_memory(messages: Vec<Message>) {
    for chunk in messages.chunks(10) {
        // 每 10 条消息调用一次 LLM
        let summary = llm.summarize(chunk).await;
        store_summary(summary).await;
    }
}
// 100 条消息 = 10 次 LLM 调用

优化方案: 增量合并

pub struct IncrementalSummarizer {
    current_summary: String,
    message_count: usize,
}

async fn consolidate_incremental(new_messages: Vec<Message>) {
    // 只在累积到阈值时才调用 LLM
    self.message_count += new_messages.len();
    
    if self.message_count < 20 {
        // 简单拼接,不调用 LLM
        self.current_summary.push_str(&format_messages(new_messages));
        return;
    }
    
    // 达到阈值,调用 LLM 压缩
    self.current_summary = llm.summarize(&self.current_summary).await;
    self.message_count = 0;
}

// 100 条消息 = 5 次 LLM 调用 (vs 原来 10 次)
// 减少 50% LLM 调用!

📊 优化效果总结

优化项 原始请求数 优化后请求数 减少比例 优先级
向量检索合并 4 3 25% ⭐⭐⭐⭐⭐
FAQ Bloom Filter 4 + LLM 0 100% ⭐⭐⭐⭐⭐
批量 Embedding N 1 90%+ ⭐⭐⭐⭐
热度批量更新 200 3 98.5% ⭐⭐⭐
相似度分层 1000 计算 300 计算 70% ⭐⭐⭐⭐
增量合并 10 LLM 5 LLM 50% ⭐⭐⭐

🎯 实施建议

Phase 1 (立即实施)

  1. ✅ FAQ Bloom Filter - 最高 ROI
  2. ✅ 向量检索合并 - 简单有效
  3. ✅ Embedding 缓存 - 低成本高收益

Phase 2 (1-2 周)

  1. 批量 Embedding 生成
  2. 热度批量更新
  3. 相似度分层过滤

Phase 3 (未来)

  1. 增量记忆合并
  2. 预测性预加载
  3. 智能缓存淘汰