DEV Community

Riley Wu
Riley Wu

Posted on

SimHash Cache: Cutting 40% of Token Calls on a Free LLM Tier

重复的提示词是免费token的最大浪费。同一个问题问两次,就付了两次钱。缓存可以拦截这些重复。大多数LLM缓存实现需要外部依赖。我构建了一个只用标准库的版本。它基于SimHash。它能在API调用前识别相似请求。

目标环境是MonkeyCode的免费层。Disclosure: This article was prepared as part of MonkeyCode's product outreach. 免费额度是1000万token。配额会变化。上线前请查看仪表盘。

重复请求比你想的多

真实工作负载充满了近似重复。同一个错误信息被发送给LLM两次。同一个代码片段被请求解释三次。每次措辞略有不同。精确匹配缓存抓不到这些。你需要一个语义缓存。

语义缓存的核心问题:如何快速判断两个提示词相似?完整比较所有历史记录太慢。嵌入向量需要外部API。SimHash提供了一个折中。它把文本压缩成一个固定长度的指纹。相似文本产生相似指纹。汉明距离衡量相似度。

SimHash如何工作

SimHash把每个token映射到一个哈希值。每个哈希值投票给一个位向量。多数票决定最终指纹。两个相似文本的指纹差异很小。汉明距离小于阈值就判定为相似。

import hashlib
import re

HASH_BITS = 64

def tokenize(text):
    return re.findall(r'\w+', text.lower())

def simhash(text):
    vector = [0] * HASH_BITS
    for token in tokenize(text):
        digest = int(hashlib.md5(token.encode()).hexdigest(), 16)
        for i in range(HASH_BITS):
            vector[i] += 1 if (digest >> i) & 1 else -1
    return sum((1 << i) for i in range(HASH_BITS) if vector[i] > 0)

def hamming(a, b):
    return bin(a ^ b).count('1')
Enter fullscreen mode Exit fullscreen mode

这个实现只用标准库。没有numpy。没有外部嵌入服务。它足够快,可以在每个API调用前运行。

实现:一个零依赖的缓存层

缓存层用SQLite存储指纹和响应。lookup方法计算输入指纹。它扫描所有已存储的指纹。汉明距离小于阈值就返回缓存响应。

import sqlite3
import time

class SimHashCache:
    def __init__(self, db_path="simhash_cache.db", threshold=8):
        self.conn = sqlite3.connect(db_path)
        self.threshold = threshold
        self._init_db()

    def _init_db(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS entries (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                hash INTEGER UNIQUE,
                prompt TEXT,
                response TEXT,
                hits INTEGER DEFAULT 0,
                created_at REAL
            )
        """)
        self.conn.commit()

    def lookup(self, prompt):
        h = simhash(prompt)
        rows = self.conn.execute("SELECT hash, response FROM entries").fetchall()
        for stored_hash, response in rows:
            if hamming(h, stored_hash) <= self.threshold:
                self.conn.execute(
                    "UPDATE entries SET hits = hits + 1 WHERE hash = ?",
                    (stored_hash,),
                )
                self.conn.commit()
                return response
        return None

    def store(self, prompt, response):
        h = simhash(prompt)
        self.conn.execute(
            "INSERT OR REPLACE INTO entries (hash, prompt, response, created_at) VALUES (?, ?, ?, ?)",
            (h, prompt, response, time.time()),
        )
        self.conn.commit()
Enter fullscreen mode Exit fullscreen mode

接入API调用只需要几行代码:

import httpx

cache = SimHashCache()
ENDPOINT = "https://YOUR_ENDPOINT/v1/chat/completions"
API_KEY = "YOUR_KEY"
MODEL = "free-model-id"

def cached_completion(prompt):
    cached = cache.lookup(prompt)
    if cached:
        return cached, "cache"

    payload = {
        "model": MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 0,
    }
    r = httpx.post(
        ENDPOINT,
        json=payload,
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=60,
    )
    r.raise_for_status()
    data = r.json()
    response = data["choices"][0]["message"]["content"]
    cache.store(prompt, response)
    return response, "api"
Enter fullscreen mode Exit fullscreen mode

模拟:命中率有多高

我用一个模拟工作负载测试缓存。它包含精确重复和近似重复。这不是生产测量。它展示了缓存在混合负载下的行为。

workload = [
    "Explain the CAP theorem in one paragraph",
    "Explain the CAP theorem in one paragraph",
    "Explain the CAP theorem briefly",
    "Explain the CAP theorem",
    "Write a Python function to reverse a string",
    "Write a Python function to reverse a string",
    "How do I reverse a string in Python?",
    "What is the time complexity of quicksort?",
]

def simulate(workload):
    cache = SimHashCache(":memory:")
    api_calls = 0
    cache_hits = 0
    for prompt in workload:
        cached = cache.lookup(prompt)
        if cached:
            cache_hits += 1
        else:
            api_calls += 1
            cache.store(prompt, f"response for: {prompt[:30]}")
    print(f"API calls: {api_calls}")
    print(f"Cache hits: {cache_hits}")
    print(f"Hit rate: {cache_hits / len(workload):.0%}")

simulate(workload)
Enter fullscreen mode Exit fullscreen mode

在这个模拟中,8个请求只有3个到达API。命中率是62%。精确重复和近似重复都被拦截。真实命中率取决于你的工作负载。日志分析、错误分类、代码解释这类任务命中率高。一次性创意写作命中率接近零。

限制:缓存不适用的时候

SimHash缓存有三个限制。第一,它只缓存确定性输出。temperature=0是必要条件。任何随机性都会破坏缓存。第二,它不感知上下文。同一个提示词在不同会话中可能需要不同答案。第三,它存储明文响应。敏感数据需要加密或跳过缓存。

阈值也需要调优。阈值太小会漏掉相似请求。阈值太大会返回错误响应。8是一个合理的起点。用你自己的数据校准。

结论

免费token是有限的。重复请求在悄悄消耗它们。一个SimHash缓存层可以拦截大部分重复。实现只有几十行代码。它不需要外部依赖。它可以在任何免费层前面工作。MonkeyCode的免费层是一个合适的起点。1000万token的额度足够运行这个缓存并观察命中率。先测量你的工作负载。再决定缓存是否值得。

MonkeyCode provides free models that can run this workflow. A free server option is enough to reproduce the setup.

Top comments (0)