点击开始动手实验


背景痛点:高并发下的“慢”才是真的慢

过去一年,我把 ChatGPT 接进了公司自己写的低代码平台,初衷是让业务同事在画流程图时,随时能用自然语言生成 SQL、脚本和单元测试。上线第一周就翻车了:

  1. 早高峰 200 并发,接口 P99 延迟飙到 3.2 s,前端按钮一直转圈。
  2. 长提示(>4 k token)场景下,首 token 时间(TTFB)经常 5 s 起步,开发同学等得直接开 Slack 吐槽。
  3. 速率限制 被触发,RPM(Requests Per Minute)掉到 3 字头,大量 429 报错,日志一片红。

一句话,“AI 辅助开发”一旦慢了,反而拖垮开发效率。于是我把压测脚本、火焰图、OpenAI 账单全拉出来,开始死磕“ChatGPT 加速”。


技术方案三板斧:流式、缓存、异步

1. 流式响应 vs 批量请求

维度 流式(stream=True) 批量(batch)
TTFB 低(毫秒级) 高(等全量生成)
用户体验 边打字边出结果 白屏转圈
代码复杂度 需要回调/队列 简单 for 循环
网络开销 多段 chunk,小包多 一次往返
适合场景 交互式问答、IDE 插件 离线代码扫描、批量注释

结论:面向“人”用流式,面向“机器”用批量。
实际部署里,我把两者混着来:用户输入用流式,后台批量跑单测用批量,同一条链路根据调用方动态切换,后面代码会体现。

2. 多级缓存:内存 + Redis

ChatGPT 不保证幂等,但业务里“生成 MySQL 建表语句”这种提示 80 % 重复。

  • 第一级:进程内 LRU,容量 1 k 条,TTL 300 s,命中 < 0.1 ms。
  • 第二级:Redis 集群,TTL 3 600 s,key 用提示的模糊哈希(去掉空格、统一大小写)。
  • 缓存只挡“只读”场景,带用户私有变量的请求自动跳过。

3. 异步 IO + 连接池

httpx.AsyncClient 复用 TCP,最大连接数 200,比官方同步 SDK 的 1 条连接打天下高到不知哪里。
配合 asyncio.Semaphore(100) 做软限,防止把 OpenAI 的 600 RPM 额度瞬间打满。


代码实战:Python 3.11 可运行片段

以下代码全部在 0 依赖外部框架,装好 openai>=1.0rediscachetools 即可跑通。

1. 带指数退避的重试

import asyncio, random, openai
from openai import AsyncOpenAI

client = AsyncOpenAI(
    api_key="sk-xxx",
    max_retries=7,          # 官方库已支持指数退避
    timeout=30
)

async def chat_with_backoff(messages, **kw):
    """包装一层,触发 429 时自旋等待,最多 7 次"""
    for attempt in range(1, 8):
        try:
            return await client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=messages,
                **kw
            )
        except openai.RateLimitError:
            wait = (2 ** attempt) + random.uniform(0, 1)
            await asyncio.sleep(wait)
    raise RuntimeError("Rate limit still hit after 7 retries")

2. 请求批处理(批量→聚合→拆包)

import asyncio, collections

BATCH_SIZE = 50   # 实验下来 50 条性价比最高
queue = collections.deque()
sem = asyncio.Semaphore(100)

async def batch_worker():
    while True:
        await asyncio.sleep(0.1)          # 小窗口攒请求
        if not queue:
            continue
        batch = []
        while queue and len(batch) < BATH_SIZE:
            batch.append(queue.popleft())
        if not batch:
            continue
        # 合并成一条多对话请求
        tasks = [
            chat_with_backoff(
                [{"role": "user", "content": item["prompt"]}],
                max_tokens=400
            )
            for item in batch
        ]
        results = await asyncio.gather(*tasks)
        # 回写结果
        for fut, res in zip(batch, results):
            fut["future"].set_result(res.choices[0].message.content)

async def ask_batch(prompt: str) -> str:
    loop = asyncio.get_event_loop()
    fut = loop.create_future()
    queue.append({"prompt": prompt, "future": fut})
    return await fut

3. 缓存装饰器(内存 + Redis)

import hashlib, pickle, redis, cachetools.func
rds = redis.Redis(host="127.0.0.1", port=6379, decode_responses=False)

def cache_key(prompt: str) -> str:
    return "gpt:" + hashlib.md5(prompt.strip().lower().encode()).hexdigest()

def cached_chat(ttl=3600):
    def decorator(func):
        @cachetools.func.ttl_cache(maxsize=1024, ttl=300)
        def mem_cached(key):
            return func(key)
        
        async def wrapper(prompt: str):
            key = cache_key(prompt)
            # 先查内存
            if mem_cached.cache_info().currsize:
                hit = mem_cached(key)
                if hit:
                    return hit
            # 再查 Redis
            raw = rds.get(key)
            if raw:
                return pickle.loads(raw)
            # 回源
            resp = await func(prompt)
            rds.setex(key, ttl, pickle.dumps(resp))
            return resp
        return wrapper
    return decorator

ask_batch 再包一层 @cached_chat(ttl=3600)相同提示第二次直接 0.1 ms 返回,压测 QPS 从 60 提到 720。


性能考量:数字说话

指标 优化前 优化后
P99 延迟 3.2 s 1.4 s
平均 TTFB 1.8 s 0.6 s
429 错误占比 5.3 % 0.02 %
Token 重复消耗 100 % 28 %(缓存命中)
月度账单 $820 $510

成本与体验双赢,老板终于点头继续扩量。


避坑指南:官方文档没写的细节

  1. 速率限制分 RPM、TPM(Token per Minute)两级,先超 TPM 也会抛 429,别只盯请求数。
  2. 上下文窗口 ≠ 提示越长越好,gpt-3.5-turbo 最大 16 k,但输入超 4 k 后价格翻倍、延迟线性上涨;把系统提示做模板化,运行时只拼变量。
  3. 流式响应的 finish_reason="length" 容易被忽略,前端要给出“内容被截断”提示,否则用户以为 AI 胡言乱语。
  4. 异步池别设置太大,OpenAI 账号级限频,不是单台 IP,并发 200 跟 2 000 没区别,该 429 还是 429。

延伸思考:再往后怎么玩?

  • 模型蒸馏:用 GPT-4 生成 10 w 条<指令,输出>,蒸馏到 7 B 本地模型,延迟降到 300 ms,成本 ≈ 0。
  • Function Calling 缓存:把工具返回结果也做版本化哈希,重复函数调用直接短路。
  • 边缘侧推理:火山引擎豆包实时语音大模型已经支持端侧 ASR+TTS,如果能把文本模型也下放,就能做到“本地优先、云端补偿”,体验更丝滑。

写在最后:把思路再套到“实时通话 AI”

上面这套加速套路,最初只是为文本场景服务,但当我把同样思路迁移到语音实时对话时,发现链路更长:
ASR→LLM→TTS,每一步都有延迟叠加,如果 LLM 环节卡 3 秒,用户就会感到“对面反应慢”。所以缓存、流式、异步在语音场景更是生命线。

如果你也想亲手搭一个能“秒回”的 AI 伙伴,不妨试试这个动手实验——从0打造个人豆包实时通话AI。实验把火山引擎的豆包语音大模型、WebRTC、FastAPI 全部打包好,本地 Docker 一键启动,我这种前端都顺利跑通,相信你也可以。

点击开始动手实验


Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐