1. Databricks机器学习任务性能与成本优化导论
在 Databricks 上跑机器学习任务,很多人第一次遇到的核心矛盾并不是“模型不收敛”,而是“同样的模型,为什么这次跑了 3 小时、账单 40 DBU,上次只跑了 40 分钟、账单 8 DBU”。机器学习工作负载与普通 ETL 最大的区别在于:它同时消耗 计算资源(CPU/GPU、内存、Shuffle)、存储资源(Delta 表、特征表、Checkpoint、MLflow 产物)、调度资源(Job 集群、任务并发、重试)以及代码效率(Python UDF、pandas 转换、数据倾斜)。这四类资源互相牵制,任何单点优化都可能被另一处瓶颈抵消。本课程的目标,就是建立一套可度量、可定位、可复现的优化方法论,让你在 Databricks 上既能跑得快,也能花得少。
一、为什么机器学习任务特别“贵”
先看一个典型场景。假设你在 Databricks 上用 mlflow 训练一个梯度提升模型,数据来自 Delta 表 prod.features.user_behavior,规模约 8000 万行、120 列。任务链路大致是:读取 Delta → 特征工程(join、窗口、编码)→ 转成 Spark ML 的 Vector → 训练 → 评估 → 写 MLflow。这条链路里,真正“训练”的时间往往只占 20%~30%,其余时间消耗在数据读取、Shuffle、序列化和特征转换上。如果集群配置不当,比如用 8 个 worker 的通用集群去跑一个只需要 2 个 worker 的特征工程,或者用 CPU 集群去跑本可以 GPU 加速的深度学习,成本就会成倍放大。
更隐蔽的是“重复计算”。很多团队在 Notebook 里反复 display()、反复 fit(),每次触发全量重算;或者把特征工程写在 Driver 端的 pandas 里,导致数据先 collect 到单机再处理,既慢又容易 OOM。这些都不是模型问题,而是工程问题。
二、四个优化维度:计算、存储、调度、代码
我们把优化空间归纳为四个维度,它们不是并列的,而是有先后依赖关系:
- 计算维度:集群规格(CPU/GPU、内存优化/计算优化)、worker 数量、自动伸缩、Spot 实例、Photon 加速、Spark 并行度(
spark.sql.shuffle.partitions)、AQE(自适应查询执行)。 - 存储维度:Delta 表的分区与 Z-Order、文件大小(避免小文件)、缓存策略(Delta Cache / 磁盘缓存)、特征表(Feature Store)的物化与复用、Checkpoint 与 MLflow 产物的生命周期。
- 调度维度:Job 集群 vs 全用途集群、任务依赖 DAG、并发度、重试策略、集群池(Instance Pool)、按需启动与自动终止、任务级超时。
- 代码维度:避免 Python UDF(优先用 Spark SQL 内置函数)、减少
collect()、用mapInPandas/applyInPandas替代逐行 UDF、向量化、缓存中间结果、避免重复fit()。
这四个维度的关系可以用一张图来理解:计算和存储决定“单次执行”的成本,调度决定“多次执行”的浪费,代码决定“每次执行”的效率。优化顺序建议是:先看代码(最容易改、收益快),再看存储(减少 I/O 和 Shuffle),然后调计算(规格与并行度),最后优化调度(集群复用与生命周期)。
三、整体方法论:度量 → 定位 → 优化 → 验证
优化不是凭感觉调参数,而是一个闭环。第一步是度量:在 Databricks 中,Spark UI 的 Stage 详情、Task 时间分布、Shuffle Read/Write 大小、GC 时间,以及 Ganglia 的 CPU/内存曲线,都是第一手证据。同时要记录每次运行的 DBU 消耗和 wall-clock 时间,作为成本基线。第二步是定位:判断瓶颈属于哪一类。如果某个 Stage 的 Shuffle Write 特别大,说明是存储/分区问题;如果 Task 时间严重倾斜(max 是 median 的 10 倍以上),说明数据倾斜;如果 Driver 端内存持续高位,说明有 collect 或 pandas 转换。第三步是优化:按上一节的顺序,从代码到调度逐层处理。第四步是验证:用同一份数据、同一套参数重跑,对比优化前后的 Stage 时间、Shuffle 量和 DBU 消耗,确认收益真实且没有引入正确性问题。
run_id、wall_clock_min、dbu_cost。每次优化后更新,形成自己的成本曲线。这比任何“最佳实践”都更有说服力。
四、一个可参考的基线示例
假设某团队在 Databricks 上训练一个 XGBoost 模型,初始配置为:全用途集群,8 个 worker(Standard_DS3_v2,4 核 14GB),数据 8000 万行,特征工程用 Python UDF 做字符串编码,训练前 collect() 到 Driver 转 pandas。运行结果(示例假设):wall-clock 约 95 分钟,DBU 约 42。经过分析,瓶颈在 UDF 和 collect。优化后:改用 Spark SQL 内置 when/otherwise 编码,训练数据保持分布式,用 XGBoostRegressor 的 Spark 接口;集群改为 Job 集群 + 4 个 worker + Photon。重跑后(示例假设):wall-clock 约 28 分钟,DBU 约 9。这个示例说明,大部分成本节省来自代码和调度,而不是单纯“加机器”。
五、本课程的学习路径
后续章节会沿着“度量—定位—优化—验证”的主线展开:先讲如何读 Spark UI 和 Databricks 的账单指标,再分别深入计算、存储、调度、代码四个维度的具体手段,然后进入特征工程、超参搜索、分布式训练、模型部署等典型场景的优化案例,最后给出成本治理与团队协作的落地方法。每一节都会尽量给出可操作的参数、配置片段和判断依据,而不是停留在概念层面。