引言
EMR Serverless Spark AI Function 自推出以来,已在智能智驾、具身智能、互联网等多个行业落地,服务了大批生产客户。在此前的内容中,我们介绍了 AI Function 的基本用法与核心能力;本篇作为延续,进一步解读并发控制、算子下推等执行机制,并聚焦一个随数据规模增长愈发关键的问题:如何控制 AI 任务的整体计算成本。
一项大规模 AI 任务的成本主要来自两部分:调用模型产生的推理费用,以及运行 Spark 作业产生的计算资源费用。围绕这两类成本,本文介绍两个作用于不同维度的降本手段——感知 AI Function 的查询优化(减少真正进入模型的调用量)与 Batch File 异步批量推理(以更低的单位价格执行推理,并释放等待阶段的计算资源),并说明它们各自的适用场景与叠加方式。
任务总成本 ≈ 模型推理成本 + Spark 计算成本
模型推理成本 ≈ 有效调用量 × 单次平均 Token × 模型单位价格
Spark 计算成本 ≈ 活跃计算资源规模 × 活跃时长 × 资源单价
| 降本维度 | 代表能力 | 适用范围 | 主要价值 |
|---|---|---|---|
| 减少有效调用量 | AI 查询优化 | 在线模式与 Batch File 模式 | 过滤无效数据,避免重复调用 |
| 降低模型单价与计算成本 | Batch File 异步批量推理 | 对时延不敏感的离线任务 | 使用批量价格,收缩等待阶段的 Executor |
大规模 AI 任务的成本为什么容易失控
在普通 Spark 查询中,过滤、投影、聚合等本地算子的单位成本相对稳定,优化器通常关注扫描量、Shuffle、Join 策略和数据倾斜。AI 函数却改变了成本结构:一次远程模型调用的延迟和价格,可能远高于同一行上的本地表达式计算;如果计算节点还需要长时间等待远端结果,Spark 资源费用也会随等待时间继续累积。
这意味着,大规模 AI 任务的成本治理至少要回答三个问题:实际需要调用多少次模型、每次调用以什么方式计费,以及 Spark 计算资源需要活跃多久。 两个逻辑等价的执行计划,最终的 AI 调用量可能相差几个数量级;同样一批请求,在线与 Batch File 不仅面向不同的时延目标和模型价格,也会形成不同的计算资源占用方式。
例如:
SELECT
review_id,
ai_sentiment(review_text) AS sentiment
FROM reviews
WHERE review_date >= '2026-01-01'
LIMIT 1000;
这条 SQL 不会"先推理全量再截取 1000 行"——Spark 会先用日期条件缩小范围,也会在拿到足够结果后停止处理。但在分布式执行中,系统仍可能提前准备候选数据,导致少量已推理的行未进入最终结果。对本地计算这微不足道,对模型调用则意味着多余的 Token 和费用。查询优化会尽量先确定真正需要的 1000 条数据再推理,让调用量更贴近最终结果规模。
因此,适用范围最广的降本动作不是"把请求发得更快",而是:先确认哪些请求根本不需要发送。
降本维度一:用 AI 查询优化减少调用量
AI 查询优化让 Spark 在计划阶段识别 AI 表达式,意识到"Project 通常是轻量本地计算"的假设不再成立,从而在保证结果语义的前提下,把便宜、确定的数据缩减尽量安排在模型调用之前。下文选取 Filter 前置、Limit 与排序协同、输入去重三类典型场景说明优化思路;引擎还内置了更多 AI 查询优化规则,能根据实际 SQL 自动协同生效。
2.1 复杂表达式下的 Filter 前置:先缩小数据,再推理
大模型调用受采样和服务状态影响,同一输入不保证每次结果相同,因此 EMR Serverless Spark 按非确定性语义处理 AI Function,避免通用规则随意重排模型调用。但这也意味着:当普通计算与 AI Function 出现在同一层时,原生 Spark 往往不会让 Filter 穿过混合计算,即使过滤条件完全不依赖模型结果。
下面这个例子中,过滤条件依赖新生成的 normalized_text,但不依赖 AI 返回的 sentiment:
SELECT
normalized_text,
sentiment
FROM (
SELECT
upper(review_text) AS normalized_text,
ai_sentiment(review_text) AS sentiment
FROM reviews
) enriched
WHERE normalized_text LIKE '%REFUND%';
AI 查询优化器能识别过滤条件与模型结果之间的依赖关系,先生成标准化文本并完成过滤,再仅对保留下来的行做情感分析。如果过滤后只剩 20% 的数据,AI 调用量也随之缩小到约 20%。反之,如果过滤条件引用了 AI 结果(如 sentiment = 'negative'),过滤就不能越过对应的 AI 计算。
2.2 Limit 与排序协同:重新权衡短路与推理成本
需要区分单独 LIMIT 和 ORDER BY ... LIMIT 两类场景。对于没有排序的 LIMIT 1000,Spark 本身会在拿到足够结果后停止处理,但分布式执行中仍可能提前准备候选数据,造成少量额外调用;查询优化会尽量先确定真正需要的数据,让调用量进一步贴近 1000。
带排序的 Limit 优化空间更大。例如"按评论时间取最新 1000 条再做情感分析",只要排序键不依赖 AI 结果,引擎就可以先完成排序与裁剪,再只对最终 1000 行调用模型。但如果查询要求"按 AI 评分排序后取前 1000 条",所有候选行的评分都可能影响排名,此时不能把 LIMIT 提前——查询优化遵循的首要原则仍是保持 SQL 语义。
下图以一条带 LIMIT 10 的查询为例,展示了优化前后的执行计划与 AI 指标对比:
未优化时,AIProject 位于 CollectLimit 之前,引擎先对 26 行候选数据逐一调用模型,再截取最终 10 行;开启查询优化后,GlobalLimit 被推至 AIProject 之前,仅对最终需要的 10 行执行推理。对应地,number of AI requests 从 26 降至 10,number of AI input tokens 从 65,572 降至 25,220,AI 请求耗时从 1.6 分钟缩短至 28.8 秒。这些指标(请求数、Token 用量、限流重试次数等)是 EMR Serverless Spark 在原生 Spark UI 基础上新增的 AI 可观测维度,用户无需额外埋点即可在作业详情中直接观察优化效果。
2.3 输入去重:相同问题只问一次
真实数据中经常存在大量重复输入,例如模板好评、重复工单、批量复制的商品描述。当同一个 AI 函数以相同参数处理相同输入时,引擎可以只计算一次,再把结果复用到对应行。"相同输入"指完整的 AI 调用语义一致——函数类型、模型服务、标签集合和推理选项都相同,结果才会复用。去重本身有额外的数据组织和结果回填开销,因此更适合重复率较高、单次调用较贵的数据集。
2.4 不止三个示例,而是一套优化体系
上述三个场景是优化思路的代表性切面,并非完整列表。EMR Serverless Spark 已围绕表达式依赖、数据缩减、结果复用和复杂查询协同等方面构建了一整套 AI 查询优化规则,可以单独生效,也可以在一条 SQL 中协同工作,但都遵循同一条原则:
在不改变查询语义的前提下,让尽可能少的数据到达 AI 计算节点。
用户看到的仍是一条普通 SQL,不需要为了节省调用而手工拆分任务或维护中间表。
查询优化回答了“能不能少调一些”,无论后续采用在线还是离线执行都能产生收益。对于必须尽快返回的任务,优化后的请求继续走在线模式;而对于能够接受非实时完成的任务,还可以进一步追问:剩下这些调用,能否用更具性价比的批量方式完成?
降本维度二:接受非实时,用 Batch File 降低模型与计算成本
3.1 Batch File 适合解决什么问题
阿里云百炼 Batch File API 面向对时延不敏感的大规模推理任务,采用异步批处理范式:先提交一批请求,由服务端在完成窗口内处理,再统一获取结果。与实时 API 的"发送一个请求、等待一个响应"不同,它把推理从同步调用变成了后台任务。
但把这套异步批处理能力接入 Spark,并不只是把多行请求拼成一个文件。批量任务可能需要较长时间才能完成;如果 Spark Task 在此期间一直占用 Executor 轮询,即使模型调用价格降低,等待过程仍会带来持续的计算资源开销。要同时降低两类成本,关键是让 Spark 计算资源不必陪着远程任务一起等待。
因此,对于离线数据管线,这种模式会同时影响两张账单:
-
模型推理费用更低:Batch 推理通常按对应实时模式价格的 50% 计费,具体支持范围和价格以百炼最新计费说明为准;
-
Spark 计算资源费用更低:异步提交完成后,Executor 不需要持续占用来等待模型结果;在 Serverless 资源策略允许时,可以收缩执行计算资源,结果就绪后再恢复计算。
3.2 异步 Batch 的三个阶段
EMR Serverless Spark AI Function 的异步 Batch 执行包含如下三个阶段:
-
提交:Spark 读取输入数据,生成批量请求并提交给模型服务,同时保留后续恢复结果所需的关联信息;
-
等待:提交完成后释放执行计算节点,由轻量的任务协调逻辑跟踪批量任务状态;
-
回收:模型服务完成推理后,Spark 恢复计算,拉取结果并与原始记录对齐,继续执行后续 SQL。
这套机制的关键价值不是“让模型更快返回”,而是把远端服务的等待时间与 Spark 计算资源解耦。在异步模式下,Executor 不需要为了轮询状态持续占用;工作空间能否缩容以及最终资源费用,仍取决于用户的 Serverless 资源策略和作业中的其他活动阶段。
状态恢复、失败处理和结果对齐由引擎统一管理。对用户来说,Batch File 仍然只是同一条 SQL 的一种执行方式,不需要额外编写上传文件、轮询任务和回填结果的脚本。
3.3 在非实时场景中,两个降本维度如何叠加
查询优化与 Batch File 可以分别使用,也可以同时生效。查询优化属于通用能力,在线与离线任务都可以受益;Batch File 则适用于业务能够接受非实时返回的离线任务。
当任务同时具备离线、规模大、成本敏感这三个特征时,两项能力可以自然叠加:只开启 Batch,仍可能把大量本可过滤或去重的数据送进模型;先通过查询优化缩减调用,再进入 Batch,才能同时降低调用量和单位价格。
在这种离线场景下,执行路径是:
-
普通 Spark 算子先完成扫描裁剪、过滤、排序和必要的数据缩减;
-
查询优化进一步减少真正需要推理的行和重复输入;
-
剩余请求再进入异步 Batch,以更低的单位价格处理;
-
结果返回 Spark,继续参与后续投影、写表和分析。
两项能力共同服务于“降本”,但适用范围与作用维度不同:查询优化负责减少调用量;Batch File 在业务接受非实时的前提下,进一步降低模型单价和计算资源成本。
实战:500 万条电商评论的双维降本
下面选择一个明确允许非实时完成的离线场景,把两项能力串起来。示例数字用于说明计算方法,不代表固定的产品性能或价格承诺。
4.1 业务背景
某电商平台在 Paimon 表中积累了 500 万条用户评论。数据团队希望完成三类标注:
-
情感倾向:正面、负面、中性;
-
问题分类:物流、质量、价格、服务等;
-
关键实体:商品名称、缺陷类型、配送时长等。
结果会写入下游分析表,用于品类洞察、客户体验分析和客服工单路由。
传统 Python 脚本不仅需要逐条调用模型,还要自行实现并发控制、限流退避、失败重试、断点续传和结果落盘。数据规模越大,这些非业务代码越容易成为主要维护成本。
4.2 用户看到的仍然是一条 SQL
-- 启用 Batch File,并使用可释放 Executor 的异步模式
SET spark.emr.serverless.ai.batchFile.enabled = true;
SET spark.emr.serverless.ai.batchFile.mode = async;
-- 数据中存在较多模板评论时启用输入去重
SET spark.emr.serverless.ai.deduplicate.enabled = true;
WITH enriched_reviews AS (
SELECT
review_id,
review_text,
to_date(review_time) AS review_date,
lower(trim(product_category)) AS normalized_category,
ai_sentiment(review_text) AS sentiment,
ai_classify(
review_text,
ARRAY('物流问题', '质量缺陷', '价格争议', '服务态度', '正面好评')
) AS issue_category,
ai_extract(
review_text,
ARRAY('product_name', 'defect_type', 'delivery_days')
) AS entities
FROM ods_user_reviews
)
INSERT INTO review_labels
SELECT
review_id,
normalized_category AS product_category,
review_text,
sentiment,
issue_category,
entities
FROM enriched_reviews
WHERE review_date >= '2026-01-01'
AND normalized_category IN ('electronics', 'home_appliance');
这条 SQL 在 CTE 中完成日期转换、品类标准化和三项 AI 标注,后续 Filter 只依赖普通派生列;执行模式、恢复流程和并发细节由引擎统一管理。
4.3 调用量是怎样降下来的
假设示例数据符合以下分布:
| 阶段 | 数据量 | 说明 |
|---|---|---|
| 原始评论 | 500 万行 | 混合计算的全量候选数据 |
| 日期与品类过滤后 | 120 万行 | 查询优化将不依赖模型结果的 Filter 前置 |
| 去除重复 AI 输入后 | 85 万条唯一输入 | 模板评论只推理一次 |
| 三个 AI 函数的最终调用量 | 255 万次 | 85 万 × 3 |
在这个混合表达式场景中,如果没有查询优化,包含非确定性 AI 调用的整体计算无法被简单拆分,500 万行数据都可能先进入三个 AI 函数,潜在调用量为 1500 万次。查询优化在确认过滤条件不依赖模型结果后,先将数据收缩到 120 万行,再结合输入去重收敛到 85 万条唯一输入。三个 AI 函数最终只需约 255 万次调用,降到未优化基线的 17%,即减少约 83%。
4.4 两个降本维度如何叠加
为了避免不同模型之间的价格差异影响比较,下面采用相对成本进行估算:
查询优化后的调用量系数 = 255 万 / 1500 万 = 17%
Batch 单位价格系数 = 50%
相对推理成本 = 17% × 50% = 8.5%
在单次平均 Token 基本相同的前提下,组合方案的模型推理费用约为“全量实时逐行调用”的 8.5%,对应约 91.5% 的理论降幅。
上述估算仅覆盖模型推理费用;Spark 计算侧的收益取决于资源规格与伸缩策略,需结合实际作业单独评估。
4.5 方案对比
| 维度 | 自建 Python 调用脚本 | 在线 AI Function + QO | 异步 Batch File + QO |
|---|---|---|---|
| 开发方式 | 自行编排 API 与状态 | SQL / DataFrame API | SQL / DataFrame API + 执行配置 |
| 示例 AI 调用量 | 取决于脚本是否主动过滤与去重 | 约 255 万次 | 约 255 万次 |
| 模型单位价格 | 实时价格 | 实时价格 | 通常为对应实时价格的 50% |
| 远端等待期 | 脚本进程持续管理 | Executor 参与在线执行 | 异步模式可释放 Executor |
| 限流与重试 | 用户自行实现 | 引擎统一处理 | 引擎统一处理 |
| 任务恢复与结果对齐 | 用户自行实现 | 引擎统一处理 | 引擎统一处理 |
| 适合场景 | 小规模定制流程 | 低延迟、交互式或持续到达的数据 | 大规模、离线、成本敏感任务 |
如何开启与调优
白名单说明:Batch File 异步批量推理功能当前处于白名单测试阶段,如需使用请联系 Serverless Spark 团队申请开通。
5.1 最小配置
如果目标是使用异步 Batch 并在等待阶段释放 Executor,需要同时开启 Batch File 和异步模式:
SET spark.emr.serverless.ai.batchFile.enabled = true;
SET spark.emr.serverless.ai.batchFile.mode = async;
如果数据重复率较高,再开启输入去重:
SET spark.emr.serverless.ai.deduplicate.enabled = true;
5.2 按 AI Function 选择执行模式
会话级配置适合整段作业采用统一策略。如果只希望为某个 AI Function 开启 Batch File,或者需要为不同处理阶段分别选择 Batch File 的同步、异步模式,也可以通过函数的 options 参数设置 batch_mode:
-- 为该 AI Function 选择异步 Batch File 模式
SELECT
review_id,
ai_sentiment(
review_text,
options => '{"batch_mode":"async"}'
) AS sentiment
FROM reviews;
batch_mode 可以设置为 "sync" 或 "async";设置为 true 时,则启用 Batch File 并沿用会话默认模式。实际使用时,建议同一个 SELECT 投影中的多个 AI Function 保持一致的 batch_mode;如果它们的时延要求不同,可以拆分为不同处理阶段。
多模态数据当前不支持 Batch File 当前 Batch File 模式暂不支持多模态数据,包括携带图片等多模态输入的
ai_query和ai_embedding_multimodal。这类调用会使用在线模式;如果它们与文本 AI Function 出现在同一个SELECT投影中,该投影也会整体按在线模式执行。建议将文本离线推理与多模态处理拆分为不同阶段。
5.3 常用参数
-- 单个批量文件的最大请求数,当前默认值为 10000
SET spark.emr.serverless.ai.batchFile.maxRequestsPerFile = 10000;
-- 批量任务完成窗口,当前默认 24h,可在支持范围内调整
SET spark.emr.serverless.ai.batchFile.completionWindow = 24h;
参数并不是越大越好。文件过大会拉长单个批次的恢复粒度,过小则增加任务和文件数量;完成窗口应根据业务 SLA 选择。
5.4 PySpark DataFrame API
from emr_serverless_spark_ai import ai_sentiment, ai_classify
from pyspark.sql.functions import col
spark.conf.set("spark.emr.serverless.ai.batchFile.enabled", "true")
spark.conf.set("spark.emr.serverless.ai.batchFile.mode", "async")
spark.conf.set("spark.emr.serverless.ai.deduplicate.enabled", "true")
reviews = spark.table("ods_user_reviews")
result = reviews.select(
col("review_id"),
ai_sentiment(col("review_text")).alias("sentiment"),
ai_classify(
col("review_text"),
["物流", "质量", "价格", "服务"],
).alias("category"),
)
result.write.mode("overwrite").saveAsTable("review_labels")
SQL 与 DataFrame API 共用同一套执行与优化能力,团队可以按现有工程习惯选择接口。
选型建议:什么时候用在线,什么时候用 Batch
Batch File 不是在线模式的替代品,两者面向不同的延迟目标。
| 判断问题 | 更适合在线模式 | 更适合异步 Batch File |
|---|---|---|
| 用户是否正在等待结果? | 是 | 否 |
| 是否要求秒级或分钟级响应? | 是 | 否,允许完成窗口 |
| 数据是否持续到达? | 流式或持续到达 | 有明确批次边界 |
| 任务规模 | 小到中等、重视即时性 | 大规模、重视吞吐与成本 |
| 计算资源是否可在等待期缩容? | 通常不关注 | 希望释放 Executor |
| 典型场景 | 交互式分析、在线服务、实时辅助 | T+1 打标、历史回填、离线 ETL、周期性内容处理 |
还有两个容易忽略的边界:
-
查询优化不只服务于 Batch。 Filter、Limit 等调用削减能力对在线模式同样有效;
-
并非所有输入形态都适合 Batch。 例如多模态请求涉及媒体读取和有效期管理,当前会使用在线执行。具体函数与模型支持范围应以实际测试为准。
总结
EMR Serverless Spark AI Function 把大模型能力带入用户熟悉的 SQL 与 DataFrame 工作流,同时让 Spark 能够理解 AI 调用是一种昂贵、需要治理的外部计算。面对不断增长的数据规模,降本需要同时关注模型服务和 Spark 计算两张账单:调用多少次、以什么价格调用,以及计算资源需要活跃多久。
本文介绍的两个降本维度分别解决了不同问题,也有不同的适用边界:
-
AI 查询优化通过 Filter、Limit、输入去重等手段,在计划阶段减少不必要的模型调用,在线与离线任务都可以受益;
-
Batch File 异步批量推理面向能够接受非实时完成的离线任务,用更具性价比的执行方式处理请求,并将远端等待与 Executor 资源占用解耦。
对于时延敏感任务,在线 AI Function 加查询优化就是完整方案;对于成本敏感的离线任务,则可以在此基础上选择 Batch File,形成“先减少调用,再降低模型单价与计算资源成本”的组合优化路径。
对数据团队而言,真正有价值的不只是把几段 API 调用改写成一条 SQL,而是把过去散落在脚本中的并发、限流、恢复、成本和资源问题,收敛为平台能够统一优化和治理的数据处理能力。这也是 Serverless Spark 从“大数据计算引擎”走向“AI 原生数据处理平台”的关键一步。