Ollama + Llama 3.3:每小时 100 条广告变体,成本 $0,再加一层预测 CTR 排序
目录
上个季度,一家中等规模的 DTC(Direct-to-Consumer,直面消费者)品牌,一个月在 GPT-4o 广告文案生产链路上烧了 $4,180。算下来每条变体大概 $0.04,听起来不多——但这个工作流每个月要产 1,000+ 条变体,才能喂饱 Meta Advantage+ 的投放管线。同一个月,我在自己的 M3 Max 笔记本上用 Ollama + Llama 3.3 70B 跑了一模一样的链路。总成本:$0。变体质量没那么好——我给它打 GPT-4o 的 80-85%。但淘汰率是一样的:反正 90% 都要扔。
80-85% 的质量差,在你本来就要砍掉 90% 的场景下,完全无所谓。在长文场景下,就要命。这正是本地模型能挣回电费的那种活。
下面把整套搭好——模型、真正干活的两个 prompt、Python 脚本、以及让这套在任意团队规模下都站得住脚的算账。
这套能跑的硬件底子
现实的本地配置:一台至少 48 GB 统一内存的 Mac。我自己在用 M3 Max 64 GB,96 GB 的 M3 Max 和 192 GB 的 M2 Ultra 都跑得动。在我的 M3 Max 上,Llama 3.3 70B 的 Q4_K_M 量化(把权重压到 4-bit 的方案,模型在盘上约 40 GB,质量有一点点损耗)——完整加载到统一内存后——每秒大约 12 个 token。习惯了云端 API 的人会觉得慢,但它是确定性的、免费的、没限速。
配置就是两条命令。装 Ollama(ollama.com),然后:
ollama pull llama3.3:70b-instruct-q4_K_M
ollama serveOllama 在 http://localhost:11434 起了一个本地 OpenAI 兼容 API(一个 HTTP 端点,接受和 OpenAI chat completions 同样格式的请求)。模型大约 40 GB,正常网络下载 6-8 分钟,加载完后常驻内存,下次请求直接调用。
Windows 或 Linux + NVIDIA 卡的配置:同一模型在 24 GB 消费级显卡(RTX 4090 等)上跑部分 CPU offload——大约 8-10 tok/s;或者在单张 48 GB 卡(RTX A6000、退役 3090 对卡等)上完整加载,15-25 tok/s。硬件成本不同,能力没差。下面的代码两边都跑得动,只换模型名和 URL。
变体生成 prompt
真正干活的 prompt。我从一个种子广告(一条标题、一段正文、一个 CTA(Call-To-Action,行动号召))出发,要 100 条不同的变体。这个 prompt 让 Llama 3.3 沿着三个维度走一个结构化的笛卡尔积(把固定变量集的所有组合都跑一遍):hook 角度、情绪色调、具体度。
你是一个专门做付费社媒的直复式(direct-response)文案。
根据下面的 SEED AD(种子广告),生成正好 {n} 条不同的变体。沿三个维度系统性地变化:
1. HOOK 角度 — {hook_angles}
2. 情绪色调 — {tones}
3. 具体度 — 具体数字/统计、具名用户、或者鲜活的感官细节
输出正好 {n} 条变体,每行一条,严格按这个格式:
HEADLINE: <8-12 个词,不要标题党,不要 emoji>
BODY: <15-25 个词,一个意思,口语化,不要感叹号>
CTA: <2-4 个词,动作动词,具体>
SEED AD:
Headline: {seed_headline}
Body: {seed_body}
CTA: {seed_cta}在生产里,三个维度我用具体列表灌进去——hook_angles: "question, stat, contrarian, story, list",tones: "urgent, calm, playful, defiant, intimate",specificity_levels: "3-4 per variant, no abstract claims"。Llama 3.3 大概 70% 的情况下会照着笛卡尔积走,剩下 30% 会悄悄重复之前的角度——这些重复的会在下游按"标题完全相等"过滤掉。
100 条变体,每条约 120 个输出 token,在我的 M3 Max 上整轮 15-20 分钟。$0 一轮,你完全可以丢到 crontab(定时任务)里夜里跑,早上起来收一份完整的牌组。同样 100 条丢给 GPT-4o 大概 90 秒,花 $4。
预测 CTR 排序 prompt
第二轮是大多数团队会跳的——也是让整条管线立得住的部分。同一个本地模型(或者任何暴露同一套 chat completions 接口的模型)对每条变体,在三个加权维度上打 0-100 分。
按三个维度给这条广告变体打分。严格一点。大部分变体会落在 30-60。
A. HOOK 强度(0-40)—— 标题在前 200 毫秒里能不能让人停下滑动?
- 0-10: 通用,和另外 50 条混在一起看不出来
- 11-20: 清楚但没特色
- 21-30: 具体,值得到下一秒钟的注意力
- 31-40: 逼着人再读一眼
B. 价值清晰度(0-30)—— 5 秒内,一个冷读者能不能讲清这个 offer?
- 0-10: 模糊或被埋了
- 11-20: 清楚但要费点劲
- 21-30: 当场明白,不用重读
C. CTA 具体度(0-30)—— 动作动词具体吗,下一步没有歧义吗?
- 0-10: "Learn more" 或通用
- 11-20: 清楚但偏软
- 21-30: 动作动词 + 具体下一步
待打分变体:
Headline: {headline}
Body: {body}
CTA: {cta}
在一行内输出三个整数,逗号分隔,再给加权总分:
A, B, C, TOTAL我按 TOTAL 留前 10-15。实战里前 10 集中在 70-85 分。50 分以下直接砍,连第二眼都不给。这个分和真实 Meta CTR(Click-Through Rate,点击率)的相关性,在我的 campaign 里大概 60-70%。不算完美,但比"凭感觉排"严格强,而且 100 条全打分只要本地推理 4 分钟左右。
prompt 故意写得短、规则写得显式,是因为:Llama 3.3 70B 跟一个数字化的评分细则走,比跟"把这 100 条从好到坏排"走要稳得多。让它对 100 条互相比较,顺序会乱;让它对单条按固定百分制细则打分,出来的数稳定且可比较。
Python 脚本
一个文件,三个函数:生成、打分、导出。除了 requests 和 csv,没有别的依赖。
pythonimport requests, csv
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "llama3.3:70b-instruct-q4_K_M"
GENERATION_PROMPT = """你是一个专门做付费社媒的直复式文案...
{上面 prompt 的其余部分}
"""
SCORING_PROMPT = """按三个维度给这条广告变体打分...
{上面 prompt 的其余部分}
"""
def generate_variants(seed, dimensions, n=100):
prompt = GENERATION_PROMPT.format(n=n, **seed, **dimensions)
r = requests.post(OLLAMA_URL, json={
"model": MODEL, "prompt": prompt, "stream": False,
"options": {"temperature": 0.9, "num_predict": 120 * n},
})
return parse_variants(r.json()["response"], n)
def score_variant(variant):
prompt = SCORING_PROMPT.format(**variant)
r = requests.post(OLLAMA_URL, json={
"model": MODEL, "prompt": prompt, "stream": False,
"options": {"temperature": 0.0, "num_predict": 30},
})
a, b, c, total = map(int, r.json()["response"].strip().split(","))
return {"hook": a, "value": b, "cta": c, "total": total}
def export_csv(variants, scores, path="variants.csv"):
with open(path, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=[
"rank", "total_score", "hook", "value", "cta",
"headline", "body", "cta_text",
])
w.writeheader()
for i, (v, s) in enumerate(
sorted(zip(variants, scores), key=lambda x: -x[1]["total"]), 1
):
w.writerow({"rank": i, **s, **v})
if __name__ == "__main__":
seed = {"seed_headline": "...", "seed_body": "...", "seed_cta": "..."}
dims = {"hook_angles": "...", "tones": "...", "specificity_levels": "..."}
variants = generate_variants(seed, dims)
scores = [score_variant(v) for v in variants]
export_csv(variants, scores)整条流水线一行命令拉起。在我的 M3 Max 上,100 条变体 + 全打分 + 导出,大概 25 分钟无人值守时间。输出是一份按预测 CTR 排好序的 CSV,前 10 名高亮。这份 CSV 可以直接进 Meta Ads Manager 批量上传,或者进 Google Ads Editor。
两个我真撞过的坑,先告诉你省你时间:
num_predict太低。 设成模型默认的 512,生成环节到第 60-70 条就被截断了。保险起见设120 * n。- 打分返回的不是整数,带了别的字。 Llama 3.3 偶尔会把分数裹在一段话里返回。解析失败就再试一次,
temperature: 0.0,system message 写严一点,第二轮基本都能返回干净的整数。
让这套站得住的账
经济账是说服老板的部分。
- GPT-4o 单条变体成本: 约 $0.04(输入+输出 token,每条约 250 token)。
- Llama 3.3 70B 本地单条成本: $0.00(电费,算上笔记本摊销大概 $0.02)。
- 同样 100 条一轮: GPT-4o $4.00,本地 $0.00。
- 同样 1,000 条一轮(一家中等 DTC 一个月用量): GPT-4o $40.00,本地 $0.00。
一个品牌每周跑 4-5 个 campaign,每月 GPT-4o 的账单是 $160-200。Mac Studio 一次性 $5,500-8,000。账面上 27-50 个月回本——听起来慢——但回本之后边际成本就是零,一个月跑 10,000 条也不带眨眼的。对一家代理公司,如果 20 个客户账户都跑这个,同一台 Mac 3-4 个月就回本了。
质量差——广告文案上相当于 GPT-4o 的 80-85%——这块我得诚实告诉你。广告文案这种活,淘汰率压倒一切。90% 反正都要扔。边际那一条不需要是 Claude 级的,只要看起来合理、彼此不同就行。长文、博客、面向客户发布的品牌文案、任何要直接过 CMO 眼睛的东西,账就反过来了——老老实实付钱用好模型。
我留 GPT-4o 在哪里用
凡是输出必须 90%+ 接近最好模型的活:长文、品牌口吻、战略定位、任何要给人审、每个字都会被人读的地方。80-85% 的差距在那种地方是命门。
凡是输出注定要被砍的活:变体生成、A/B 测试文案、邮件标题、内部命名、人设改写——本地 Llama 3.3 在经济上赢,在产出质量上"无所谓"。
我现在拿这条规则套所有活:淘汰率是多少,每条 4 美分值不值得? 淘汰率高、条数高,答案就是不值得,本地赢。2025 年我整套 AI 成本结构上最大的一次调整,不是换了一个更强的模型——是学会了哪些活不该再按云端价付钱。
Ollama / Llama 3.3 这套在生产里确实"丑"——限速的故事是"等你的笔记本",质量的故事是"够用但不出彩",支持的故事是"你自己"。但是在这条具体的活上——高产出、高淘汰、低风险——账算得太难看,老板会拍桌子。等你这套本地栈已经为这一件事转起来,后面四件顺手能上的活(标题、命名、列表清洗、批量重打标)基本就是同一个脚本换个 prompt。