大家好,我是老A。

今天,我想分享一个关于Feed流上AI推荐的成本优化方案,这篇文章的构思源于一位粉丝的咨询:如何在预算有限的情况下,不依赖昂贵的GPU资源,使用现有CPU服务器实现有效的AI个性化推荐。

首先科普一下,啥是Feed流,啥是AI推荐。

概念对齐,我们言归正传。上周,我在后台收到一位粉丝(以下简称小A)的私信,他问我:“老A,我们公司也想给App的Feed流上AI推荐,提升用户体验。但老板看了看GPU服务器的报价,一阵便秘,说预算不够。难道中小厂,就没资格玩AI了吗?我们用现有的CPU服务器,真的不行吗?”

image.png

这个问题,代表了当前环境下,无数中小厂技术兄弟们的集体困境,着实是问到了我的心坎里。

大厂的天然优势是有无限的资源去堆砌最好的硬件。中小厂就会收到成本资源的各种限制,但一个优秀工程师的真正价值,往往体现在资源受限的情况下,如何找到降本增效的最优解。

经过周末两天的思考和设计,我给出了一套低成本CPU跑AI推荐的“B面”方案,将月度成本从约1.8万元降至600元左右(参考阿里云2025年经济型ECS实例定价),接下来我会给出详细的技术方案,方案中包含了技术细节、代码示例和性能评估。敲黑板,想玩AI但没预算的兄弟们,接下来要认真听讲。

第一幕:A面——通往“破产”的AI大道

在给出我的“B面”方案前,我先解释一下,为什么AI推荐在大家印象里是个“吞金兽”。

先看看业界公认的“A面标准解决方案”,通常是这么搭配的:

  1. 选型​: 为了保证效果,直接上一个业界领先的开源大模型,比如Llama 3 8B。
  2. 部署​: 租一台专业的GPU云服务器,比如NVIDIA A100或H100。
  3. 调用​: 业务方直接调用这个GPU服务进行实时推理。

效果好吗?好、很好、非常好~ 贵吗?贵、真贵、贵得要死,老板看一眼就便秘

image.png

价格参考

服务商类型A100型号预估月租价格(人民币)
大型GPU服务商A100 40G6000 - 10000
A100 80G15000 - 18000

第二幕:B面——低成本AI的“三根救命毫毛”

我告诉小A,他的思路被业界的标准解决方案框住了。其实想在CPU上跑AI推荐是可行的,我这里有三根“救命毫毛”,每一根,都是极尽降本增效的“B面”智慧,你要是不要?

第一根毫毛:模型量化

要把大象装冰箱,拢共分几步?第一步,给大象减肥。

image.png

这是什么意思? 我把模型比作大象,模型量化,就是给大象减肥瘦身,换句话说就是给模型“减肥”。

原始的大模型,就像一张超高清的RAW格式照片,每个像素都用64位或32位的浮点数(FP32/FP16)来表示,精度极高,但体积也巨大。一个7B(70亿参数)的模型,原始体积轻松超过13GB。

而量化技术,本质是压缩算法,用更少的数据位(比如4-bit整数)来近似表示原始的浮点数,把模型体积压缩到原来的1/4甚至更小。

那如何能找到模型大小和精度之间的最佳平衡点呢,其实很多专家已经给出了最佳时间:

根据2025年的一项arXiv论文 《Interpreting the Effects of Quantization on LLMs》,4-bit量化在推荐任务中通常仅导致1-3%的性能下降,尤其是文本生成类任务,性能损耗更低。

而对于Qwen2-7B,原模型体积超过13GB;经4-bit量化(如Q4_K_M方法),可压缩至约5GB以内,适合32GB内存的CPU服务器。社区基准(如Best Ollama Models 2025)显示,这种量化在推理速度上可达原模型的80-90%,而准确性仍能保持高水准(文本累任务)。

如何量化模型?很简单,我们可以利用Ollama内置的工具,两行代码完成模型量化。

FROM qwen2:7b
PARAMETER quantization_level q4_K_M

代码就两行,很简单,我解释下。
第一行:使用 qwen2 这个有 70亿参数的模型。
第二行:模型量化到4bit的精度(q4),采用了K_M策略。
这样,我们就把一个需要“大象笼子”(昂贵GPU)才能关住的模型,压缩成了一个普通“小冰箱”(32G内存服务器)就能容纳的大小。

第二根毫毛:Ollama —— 驯服大模型的神

image.png

Ollama:CPU推理引擎

如果把模型比作“猛兽”,那么Ollama就是驯兽师。Ollama是一个开源的推理服务器,能把复杂的AI模型,封装成一个极其简单的、标准的HTTP服务。相比其他开源工具(如ONNXRuntime或HuggingFace Transformers),Ollama在优化CPU推理上表现更优秀,支持直接运行量化模型,同时避开了复杂的依赖问题。

实测性能​:根据Reddit上的社区基准,Ollama在Intel/AMD CPU上的Qwen2-7B 4-bit推理性能稳定,P99延迟约800-1200ms,QPS达8-12(视CPU核心数)。我在单台8核vCPU、32GB内存云服务器上,将Qwen2-7B模型量化后,其平均响应延迟控制在850ms,P99延持1170ms,稳定性高且足够支撑非实时推荐场景。

来看一下调用Ollama的核心代码有多简单:

# -----------------------------------------------------------------
# 调用Ollama的核心示例代码
# -----------------------------------------------------------------

import requests
import json

# Ollama 服务地址:替换为实际的服务部署地址和端口信息
OLLAMA_API_URL = "http://your_server_ip:11434/api/generate"

# 指定使用的模型名称,确保该模型是你已部署的,并支持进行生成任务
MODEL_NAME = "my-quantized-qwen2:7b"  

def get_ai_feed_recommendation(post_content: str) -> str:
    """
    调用 Ollama API 为内容生成推荐语,用于引导用户点击或阅读。

    参数:
        post_content (str): 原内容,需要生成推荐语的主文案。

    返回:
        str: 一句简洁、有吸引力且符合内容的推荐语。如果发生错误,则返回通用的推荐文案。
    """
    # 1. 准备生成提示 (Prompt)
    # 定义清晰的角色和具体执行要求,引导模型生成符合内容属性的推荐语
    prompt = f"你是一名资深的社交媒体内容编辑,请基于以下内容生成一条简短且吸引眼球的推荐语,限制在25个字以内。内容:'{post_content}'"

    # 2. 构建 API 请求的负载
    payload = {
        "model": MODEL_NAME,
        "prompt": prompt,
        "stream": False  # 关闭流式响应,等待完整生成结果
    }

    print("--- 开始请求 Ollama 服务 ---")
    print(f"使用的模型: {MODEL_NAME}")
    print(f"提交的 Prompt: {prompt}")

    try:
        # 3. 发起 HTTP POST 请求到 Ollama 服务
        response = requests.post(
            OLLAMA_API_URL, 
            json=payload, 
            timeout=15  # 请求超时时间设为 15 秒
        )

        # 检查响应状态码,状态异常时抛出 HTTPError
        response.raise_for_status()

        # 4. 解析返回的 JSON 响应
        response_data = response.json()
        recommendation = response_data.get("response", "").strip()

        if not recommendation:
            # 当返回结果为空时提供降级文案,避免用户体验受到影响
            print("警告: 模型返回内容为空。")
            return "发现精彩内容,点击查看!"

        return recommendation

    except requests.exceptions.Timeout:
        # 请求超时时的异常处理
        print("错误: 请求 Ollama API 时超时。")
        return "请求超时,请稍后重试!"

    except requests.exceptions.RequestException as e:
        # 处理所有其他网络异常,并返回降级文案
        print(f"错误: 调用 Ollama API 时发生网络错误: {e}")
        return "热门内容,不容错过!"

    except json.JSONDecodeError:
        # 捕获返回数据无法解析为 JSON 的情况
        print("错误: 无法解析 Ollama 响应,返回结果并非有效 JSON 格式。")
        return "服务响应异常,请重试。"


# --- 主程序入口 --- #
if __name__ == "__main__":
    # 示例内容
    sample_post = (
        "今天分享一个用Python自动化办公的技巧,只需要10行代码,就能自动整理散落在几十个文件夹里的Excel文件,并合并成一个总表,大大提高了工作效率。"
    )

    print("--- 开始测试 ---")
    print(f"原内容:n{sample_post}n")

    # 调用推荐函数获取生成的推荐语
    ai_recommendation = get_ai_feed_recommendation(sample_post)

    print("n--- 已获取推荐语 ---")
    print(f"AI 生成的推荐语: 【{ai_recommendation}】")
    print("n--- 测试结束 ---")

    # 第二个测试内容
    # sample_post_2 = "旅行Vlog:探索冰岛的蓝冰洞,仿佛进入了地球的内心,每一帧都是壁纸!"
    # print(f"n原内容:n{sample_post_2}n")
    # ai_recommendation_2 = get_ai_feed_recommendation(sample_post_2)
    # print("n--- 已获取推荐语 ---")
    # print(f"AI 生成的推荐语: 【{ai_recommendation_2}】")

第三根毫毛:缓存推理结果 —— 不让CPU“白忙活”

image.png

CPU推理虽然便宜,但并不快。我实测一个请求的P99延迟在800ms左右。如果每个用户每次刷新,都去重新计算一次,服务器很快就会被打爆。

所以,AI推理的结果我们必须要缓存。那么用什么缓存比较好呢?我选择RedisJSON

为什么是RedisJSON?

Redis本身在缓存领域就是大哥级别的存在,而RedisJSON扩展支持存储结构化数据(如复杂JSON对象)。所以在AI推荐这个业务场景下RedisJSON就是一个神器。它让我们可以把AI生成的丰富结构的JSON数据,直接存入Redis,同时附加生成时间、来源模型等信息,这样可以极大的降低重复推理的成本。

实测用户刷新推荐case,Redis缓存读写延迟平均仅需3-5ms。相比直接推理服务的大幅超过800ms的延迟,缓存方案优化了用户体验的同时也降低了服务器压力。

# -----------------------------------------------------------------
# 用 Pipeline 原子性地缓存推荐数据到 RedisJSON
# -----------------------------------------------------------------

import redis
from datetime import datetime

# --- 配置相关 ---
REDIS_HOST = 'localhost'  # Redis 主机地址
REDIS_PORT = 6379         # Redis 端口
REDIS_DB = 0              # Redis 数据库编号

# --- 建立 Redis 连接 ---
# decode_responses=True 用来把 Redis 的返回值解码成字符串
try:
    redis_client = redis.Redis(
        host=REDIS_HOST,
        port=REDIS_PORT,
        db=REDIS_DB,
        decode_responses=True
    )
    # 测试是否可以正常连接
    redis_client.ping()
    print("已成功连接到 Redis 服务。")
except redis.exceptions.ConnectionError as e:
    print(f"无法连接到 Redis:{e}")
    exit()  # 连接失败,直接退出程序


def cache_recommendation_in_redis(post_id: int, recommendation_text: str, model_name: str, ttl_seconds: int = 3600):
    """
    将推荐内容以 JSON 格式原子性地存入 Redis,并设置过期时间。

    Args:
        post_id (int): 内容的唯一 ID。
        recommendation_text (str): AI 生成的推荐文案。
        model_name (str): 生成推荐文案的模型名称。
        ttl_seconds (int): 缓存过期时间(秒)。默认 3600 秒(1 小时)。
    """
    # Redis 的 Key 命名采用冒号分隔,避免混淆
    key = f"feed:recommendation:{post_id}"
    
    # 要存储的数据内容
    data_to_cache = {
        "text": recommendation_text,
        "source_model": model_name,
        "generated_at": datetime.utcnow().isoformat() + "Z",  # UTC 时间戳(ISO 8601 格式)
    }

    try:
        # 使用 Redis Pipeline 保证多个操作的原子性
        pipe = redis_client.pipeline()

        # 包装 JSON 数据写入和过期时间设置
        pipe.json().set(key, '$', data_to_cache)  # '$' 表示写入 JSON 根路径
        pipe.expire(key, ttl_seconds)

        # 一次性提交所有操作命令,并获取执行结果
        results = pipe.execute()

        # 检查命令返回值(如 JSON.SET 成功通常返回 True)
        if all(results):
            print(f"推荐内容已成功缓存到 Redis,Post ID: {post_id}")
            return True
        else:
            print(f"缓存 Post ID {post_id} 时部分操作失败,详情:{results}")
            return False
    except redis.exceptions.RedisError as e:
        # 捕捉并处理 Redis 相关错误
        print(f"Redis 存储失败,错误详情:{e}")
        return False


# --- 主程序,用于测试 ---
if __name__ == "__main__":
    # 准备一些测试用数据
    test_post_id = 101
    test_recommendation = "用 10 行代码自动整理 Excel,效率神器了解一下!"
    test_model = "my-quantized-qwen2:7b"

    print("n--- 开始缓存测试 ---")

    # 调用函数,将测试数据存入 Redis
    success = cache_recommendation_in_redis(
        post_id=test_post_id,
        recommendation_text=test_recommendation,
        model_name=test_model
    )

    # 如果写入成功,尝试取回验证
    if success:
        print("n--- 验证缓存内容 ---")
        cache_key = f"feed:recommendation:{test_post_id}"
        
        # 从 Redis 获取刚刚写入的 JSON 数据
        retrieved_data = redis_client.json().get(cache_key)
        print(f"从 Redis 获取的内容:n{retrieved_data}")

        # 查看该 key 的剩余过期时间(TTL)
        ttl = redis_client.ttl(cache_key)
        print(f"缓存剩余有效期:{ttl} 秒(应接近 3600 秒)")

        if retrieved_data and ttl > 0:
            print("验证成功:数据已正确存储,并配置了过期时间!")

    print("n--- 测试结束 ---")

第三幕:降本开始——“600块方案”完整复现

理论讲完了,我们来实操,是骡子是马,咱得拉出来遛遛。

image.png

1. 资源清单

云服务器: 一台8核CPU、32G内存的通用或经济型云服务器。我们的目标,就是把它榨干。
核心软件: Docker和最新版的Ollama。
AI模型: 一个经过4-bit量化的7B级模型,如Qwen2-7B-Instruct-Q4_K_M。

2. 成本清单

这是大家最关心的部分,600块到底是怎么来的?

成本项A面方案 (GPU)B面方案 (我们的CPU方案)
核心硬件NVIDIA A100 GPU云服务器8核32G CPU云服务器
预估月租约 ¥18,000约 ¥600
开源模型¥0¥0
成本降幅-暴降 96.7%

3. B面真话:成本与性能间的平衡

但光说成本,不谈性能,妥妥的耍流氓,所以咱们一起看看性能这块。600块的成本,必然是要付出其他代价的,我们的代价,主要体现在延迟和吞吐量上。

性能指标A面方案 (GPU)B面方案 (我们的CPU方案)
P99 延迟约 100ms约 800-1200ms
QPS (并发)>100 次/秒约 8-12 次/秒
适用场景强实时、大规模非实时、中小规模、可缓存

4. 生产级K8s部署脚本

本地跑通只是第一步,工程师要考虑的是如何让项目活下去,如何高可用

下面是我为这套方案准备的一份生产级部署脚本

# 在K8s集群中,部署两个Ollama服务的副本,并为它们分配合理的CPU和内存资源
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama-service
spec:
  replicas: 2 # 两个副本实现基本的高可用
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      containers:
      - name: ollama
        image: ollama/ollama:latest
        ports:
        - containerPort: 11434
        resources:
          requests:
            cpu: "4" 
            memory: "16Gi"
          limits:
            cpu: "8"
            memory: "30Gi"
# 为上面部署的Ollama副本,创建一个集群内部稳定的访问地址。
apiVersion: v1    
kind: Service
metadata:
  name: ollama-api
spec:
  selector:
    app: ollama
  ports:
    - protocol: TCP
      port: 11434
      targetPort: 11434
  type: ClusterIP

第四幕:把你打过的“胜仗”变成“价值报告”

到此,我已经用一套完整的技术方案回答了小A的问题。

是的,在没有GPU预算的情况下,中小厂完全有资格玩AI,甚至可以玩得很出彩。

但故事到这里,只讲了一半。一个能将AI推荐成本降低97%的技术方案,如果不能被你清晰地呈现在简历和面试中,那么这个项目你做了等于没做,价值为0。那么,如何把这种牛逼的降本增效经历,变成你简历中的价值报告呢?

如何把它写进简历?

你需要把它浓缩成一个能同时征服HR、业务主管和技术主管的项目描述。关于这套具体的“精修”方法,我在上一篇文章里,有详尽的、手把手的教学:

延伸阅读:《我帮一位“4年经验”的粉丝改了12次简历,大厂HR看完秒约》

如何在面试中把它讲出来?

当面试官让你展开讲讲这个项目时,你需要用一套结构化的“故事框架”,来展现你解决问题的完整逻辑链。这套框架,我在我的“面试篇”里有过详细拆解:

延伸阅读:《面试官:“聊聊你最复杂的项目?” 为什么90%的候选人第一句就栽了?》

尾声:老A的感悟

我们回到最初小A的问题上。他的困境,本质上不是技术困境,而是一种思维困境。官方的标准解决方案,让我们习惯性地认为,解决难题,就必须依赖最牛逼、最昂贵的武器。

但技术的B面真相是:一个真正牛Bee的工程师的牛Bee之处在于,能在资源受限的情况下,用有限的成本,解决无限的业务问题

当然,要把这些原则完美地应用到自己的简历和面试中,需要大量的经验,如果你即将迎来关键的求职窗口,想深入探讨自己的技术和职场难题,可以关注我的同名公众号“大厂码农老A”,在后台回复“简历”,我们可以一起聊聊。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]