平安好医生走一走计步器
153.52MB · 2025-09-26
大家好,我是老A。
今天,我想分享一个关于Feed流上AI推荐的成本优化方案,这篇文章的构思源于一位粉丝的咨询:如何在预算有限的情况下,不依赖昂贵的GPU资源,使用现有CPU服务器实现有效的AI个性化推荐。
首先科普一下,啥是Feed流,啥是AI推荐。
概念对齐,我们言归正传。上周,我在后台收到一位粉丝(以下简称小A)的私信,他问我:“老A,我们公司也想给App的Feed流上AI推荐,提升用户体验。但老板看了看GPU服务器的报价,一阵便秘,说预算不够。难道中小厂,就没资格玩AI了吗?我们用现有的CPU服务器,真的不行吗?”
这个问题,代表了当前环境下,无数中小厂技术兄弟们的集体困境,着实是问到了我的心坎里。
大厂的天然优势是有无限的资源去堆砌最好的硬件。中小厂就会收到成本资源的各种限制,但一个优秀工程师的真正价值,往往体现在资源受限的情况下,如何找到降本增效的最优解。
经过周末两天的思考和设计,我给出了一套低成本CPU跑AI推荐的“B面”方案,将月度成本从约1.8万元降至600元左右(参考阿里云2025年经济型ECS实例定价),接下来我会给出详细的技术方案,方案中包含了技术细节、代码示例和性能评估。敲黑板,想玩AI但没预算的兄弟们,接下来要认真听讲。
在给出我的“B面”方案前,我先解释一下,为什么AI推荐在大家印象里是个“吞金兽”。
先看看业界公认的“A面标准解决方案”,通常是这么搭配的:
效果好吗?好、很好、非常好~ 贵吗?贵、真贵、贵得要死,老板看一眼就便秘。
价格参考
服务商类型 | A100型号 | 预估月租价格(人民币) |
---|---|---|
大型GPU服务商 | A100 40G | 6000 - 10000 |
A100 80G | 15000 - 18000 |
我告诉小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%,而准确性仍能保持高水准(文本累任务)。
如何量化模型?很简单,我们可以利用Ollama内置的工具,两行代码完成模型量化。
FROM qwen2:7b
PARAMETER quantization_level q4_K_M
代码就两行,很简单,我解释下。
第一行:使用 qwen2 这个有 70亿参数的模型。
第二行:模型量化到4bit的精度(q4),采用了K_M策略。
这样,我们就把一个需要“大象笼子”(昂贵GPU)才能关住的模型,压缩成了一个普通“小冰箱”(32G内存服务器)就能容纳的大小。
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推理虽然便宜,但并不快。我实测一个请求的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--- 测试结束 ---")
理论讲完了,我们来实操,是骡子是马,咱得拉出来遛遛。
云服务器: 一台8核CPU、32G内存的通用或经济型云服务器。我们的目标,就是把它榨干。
核心软件: Docker和最新版的Ollama。
AI模型: 一个经过4-bit量化的7B级模型,如Qwen2-7B-Instruct-Q4_K_M。
这是大家最关心的部分,600块到底是怎么来的?
成本项 | A面方案 (GPU) | B面方案 (我们的CPU方案) |
---|---|---|
核心硬件 | NVIDIA A100 GPU云服务器 | 8核32G CPU云服务器 |
预估月租 | 约 ¥18,000 | 约 ¥600 |
开源模型 | ¥0 | ¥0 |
成本降幅 | - | 暴降 96.7% |
但光说成本,不谈性能,妥妥的耍流氓,所以咱们一起看看性能这块。600块的成本,必然是要付出其他代价的,我们的代价,主要体现在延迟和吞吐量上。
性能指标 | A面方案 (GPU) | B面方案 (我们的CPU方案) |
---|---|---|
P99 延迟 | 约 100ms | 约 800-1200ms |
QPS (并发) | >100 次/秒 | 约 8-12 次/秒 |
适用场景 | 强实时、大规模 | 非实时、中小规模、可缓存 |
本地跑通只是第一步,工程师要考虑的是如何让项目活下去,如何高可用。
下面是我为这套方案准备的一份生产级部署脚本
# 在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的问题上。他的困境,本质上不是技术困境,而是一种思维困境。官方的标准解决方案,让我们习惯性地认为,解决难题,就必须依赖最牛逼、最昂贵的武器。
但技术的B面真相是:一个真正牛Bee的工程师的牛Bee之处在于,能在资源受限的情况下,用有限的成本,解决无限的业务问题。
当然,要把这些原则完美地应用到自己的简历和面试中,需要大量的经验,如果你即将迎来关键的求职窗口,想深入探讨自己的技术和职场难题,可以关注我的同名公众号“大厂码农老A”,在后台回复“简历”,我们可以一起聊聊。