一个“不可能完成”的任务

大家好,我是老A。

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

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

Feed流
也叫瀑布流,主打的就是一个源源不断,以持续更新、滚动浏览的形式呈现内容。我们平常使用的社交媒体(如微博、抖音、今日头条、Ins、FB)、新闻客户端(如今日头条)、视频平台(如油管、网飞)以及电商应用(如淘宝、亚马逊)的主界面,都是典型的Feed流。它的特点是内容源源不断,用户可以通过向下滑动来浏览新的信息。

AI推荐
通过算法向用户推荐内容,提升用户粘性,说白了就是抓住你的喜好,让你越看越爱看,沉溺其中不能自拔,抖音3分钟,现实1小时。专业点说就是通过复杂的算法模型,分析和预测用户的兴趣,从而实现个性化的内容推荐。目标说的好听点是帮助用户快速发现对自己有价值的内容,其实本质就是帮助平台提升用户粘性和活跃度。

概念对齐,我们言归正传。上周,我在后台收到一位粉丝(以下简称小A)的私信,他问我:“老A,我们公司也想给App的Feed流上AI推荐,提升用户体验。但老板看了看GPU服务器的报价,一阵便秘,说预算不够。难道中小厂,就没资格玩AI了吗?我们用现有的CPU服务器,真的不行吗?”
在这里插入图片描述
这个问题,代表了当前环境下,无数中小厂技术兄弟们的集体困境,着实是问到了我的心坎里。

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

经过周末两天的思考和设计,我给出了一套低成本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服务进行实时推理。

效果好吗?好、很好、非常好~ 贵吗?贵、真贵、贵得要死,老板看一眼就便秘
在这里插入图片描述

老A点评​: 我去主流云厂商那儿随便看了下,一台(注意是一台)入门级的A100 80G的GPU服务器,包月价格普遍在1.5万到2万之间。还没算上网络带宽和运维的费用。对于一个还没开始用AI赚钱的中小厂来说,这条路,基本上就是通往“破产”的AI大道。

价格参考

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

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

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

第一根毫毛:模型量化

要把大象装冰箱,拢共分几步?第一步,给大象减肥。
在这里插入图片描述
这是什么意思?
我把模型比作大象,模型量化,就是给大象减肥瘦身,换句话说就是给模型“减肥”。

原始的大模型,就像一张超高清的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%,而准确性仍能保持高水准(文本累任务)。

老A点评:之前工作中有个AI项目我就进行过模型量化的调研,一开始我尝试了6-bit量化,希望能在CPU推理环境下多保留一些精度。但实际结果让人很失望,生成推荐语中多处出现语意误解,甚至简短文案也变得生硬且带有明显的逻辑错误。最终转向4-bit量化后,效果才达到了预期。这也反向印证了业界最佳时间的准确性。

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

FROM qwen2:7b
PARAMETER quantization_level q4_K_M

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

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

在这里插入图片描述
Ollama:CPU推理引擎

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

老A点评:Ollama真是救了我一命,之前我试着在本地环境搭模型,结果一直被各种依赖报错折腾得要疯。相比之下,Ollama真的太简洁太好用了。

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

老A点评: Ollama把一个反复无常的AI模型,“驯服”成了一个稳定、可靠的后端服务。

来看一下调用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“白忙活”

在这里插入图片描述

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

老A说:最昂贵的计算,就是重复的计算。

所以,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块方案”完整复现

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

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%

老A点评: 我去阿里云官网看了一眼,一台通用的ecs.g7.2xlarge(8核32G)实例,月租大概是调用Ollama的核心代码。而一台性能稍弱的ecs.u1或经济型ecs.e实例,则完全可以控制在600元上下。
对于中小厂的概念验证阶段或初期业务阶段,用最低的成本跑通MVP,才是最务实的选择。

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

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

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

老A点评:能看到CPU方案的延迟,确实远高于GPU。但是对于Feed流推荐语生成这种可以被缓存的非强实时场景,1.2秒的后台计算延迟,是可以接受的。而8-12的QPS,对于一个“小作坊”级别的应用(日活约2000-5000)来说,在有Redis缓存顶着的情况下,也是足够了,这就是花小钱办大事😄。

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说:AI时代下的工程师能力,比拼的不是你花钱的能力,而是省钱和创造价值的能力。

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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐