ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ZenML Pro 资源池实战示例手册:从 ResourceSettings 到抢占、队列与多池分配

ZenML Pro 资源池实战示例手册:从 ResourceSettings 到抢占、队列与多池分配 ZenML Pro 资源池实战示例手册从 ResourceSettings 到抢占、队列与多池分配【免费下载链接】zenmlZenML : One AI Platform from Pipelines to Agents. https://zenml.io.项目地址: https://gitcode.com/GitHub_Trending/ze/zenml本篇是 ZenML Pro 资源池Resource Pool功能的场景化实战手册围绕池Pool定义共享容量、策略Policy规定某个编排器/步骤执行器如何取用、步骤通过ResourceSettings声明需求这三层模型展开。文中每个小节都是一个独立可复现的场景逐一展示服务端在分配、排队、借用、抢占与拒绝时做出的决策。读完本文你将能准确预测任意ResourceSettings 池容量 策略组合在 ZenML Pro 服务器端的最终结果并能用zenml resource-pool/zenml resource-requestCLI 亲自验证。资源池属于 ZenML 付费能力且仅对动态流水线dynamic pipelines生效。建议先阅读同目录下的 资源池总览、核心概念 与 资源池对账reconciliation流程再进入本手册。阅读约定与默认假设把本页当作一门短课程每个小节是一个自洽的独立场景你始终会看到三样东西——池共享容量、策略某个编排器或步骤执行器如何取用该池、步骤ResourceSettings声明的需求随后是服务端的具体行为。除非另有说明以下假设始终成立步骤默认是可抢占preemptible的除非显式写preemptibleFalse。这一默认值在源码中也得到确认ResourceSettings.preemptible: bool True见 src/zenml/config/resource_settings.py。当我们说没有其他工作在运行时意味着同一时刻只有一个步骤运行方便你把注意力集中在单个决策上。策略reserved与limit里的每一个键都必须存在于池的容量中。你无法在策略中计量池没有定义的资源。如果一个编排器或步骤执行器绑定了多个池的策略步骤仍然最多从一个池获得一次分配。整个请求必须在该池上完全合格单个步骤的资源不会被拆到多个池。reserved、limit、priority的精确定义见 核心概念抢占的受害者排序规则见 How preemption works。Primer从ResourceSettings到资源请求假设一个步骤声明如下from zenml.config import ResourceSettings ResourceSettings( gpu_count2, cpu_count4, memory16GiB, pool_resources{tensorrt_sessions: 1}, preemptibleTrue, )ZenML 会把它转换成一个资源请求转换关系大致如下来源请求键数值如何推导示例值gpu_countgpu与gpu_count相同2cpu_countmcpuceil(cpu_count * 1000)4000memorymemory_mb转换为 MB17180对应16GiBpool_resources你的自定义名原样拷贝并与类型化字段合并1服务端step_run每个步骤恒为 11这段转换逻辑在源码中可以直接看到ResourceSettings.merged_requested_resources()方法会把gpu_count写入gpu键、把math.ceil(cpu_count * 1000)写入mcpu键、把内存换算成 MB 后向上取整写入memory_mb键同时以pool_resources字典作为合并起点见 src/zenml/config/resource_settings.py。内存字符串的解析由ByteUnit枚举与MEMORY_REGEX负责校验见 src/zenml/config/resource_settings.py因此memory必须形如16GiB、4GB这类数字单位的写法。容量匹配规则池必须为gpu、tensorrt_sessions这类有界键定义容量。如果池里没有mcpu、memory_mb、step_run的行这三个维度在池层面是无界的见下文示例。对于其他所有键池中缺失行意味着容量为零正向请求会被拒绝。如果想让策略对某个键设置reserved/limit该键必须先出现在池上——策略键永远是池键的子集。热身单池、单策略、只有 GPU可抢占步骤借用超出 reserved 的容量故事团队有属于自己的 4 块 GPU但池是空的。他们申请 6 块 GPU 且允许抢占可以借用两块空闲 GPU。池{ name: datacenter-gpus, capacity: { gpu: 8 } }策略编排器team-ml-orch挂接到该池{ pool: datacenter-gpus, component: team-ml-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } }步骤from zenml import step from zenml.config import ResourceSettings step( settings{ resources: ResourceSettings( gpu_count6, preemptibleTrue, ) } ) def train() - None: ...结果立即分配不排队。4 块 GPU 计入策略的 reserved 份额2 块是从空闲池容量中借用的处于 reserved 与 limit 之间且池仍有空闲单元。非抢占步骤保持在 reserved 之内故事同样的池与策略。生产任务要 2 块 GPU 并选择不抢占。2 在 4 的预留额度之内。池{ name: datacenter-gpus, capacity: { gpu: 8 } }策略{ pool: datacenter-gpus, component: team-ml-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } }步骤step( settings{ resources: ResourceSettings( gpu_count2, preemptibleFalse, ) } ) def production_train() - None: ...结果分配成功假设没有其他竞争。非抢占任务要求每个键满足请求 ≤ reserved2 ≤ 4通过。非抢占步骤超出 reserved故事同样的池与策略。生产任务要 6 块 GPU 但拒绝抢占。非抢占工作不能使用 reserved 上方的借用区间。池{ name: datacenter-gpus, capacity: { gpu: 8 } }策略{ pool: datacenter-gpus, component: team-ml-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } }步骤step( settings{ resources: ResourceSettings( gpu_count6, preemptibleFalse, ) } ) def too_large_production_train() - None: ...结果立即拒绝动态运行快速失败。gpu的 6 超出 reserved4非抢占请求不能借用至 limit。这里的教训与对账文档中的结论一致非抢占任务想要更大的单步请求必须提高reserved而不是limit见 How preemption works。CPU、内存与步骤槽位无界 vs 计量仅 GPU 的池——CPU 与内存不做配额故事你只在池和策略里建模了 GPU。步骤仍会在请求中携带mcpu与memory_mb但这两个键在池层面被省略时是无界的本策略也省略了它们——因此它们不会阻塞非抢占工作。池{ name: training, capacity: { gpu: 4 } }策略{ pool: training, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 2 }, limit: { gpu: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count32, memory64GiB, preemptibleFalse, ) } ) def hungry_but_ok_on_gpu() - None: ...结果只要没有其他问题就分配。这里只对gpu做门禁mcpu/memory_mb/step_run在该模式下不受池或策略限制。CPU 和内存保持仅供参考状态直到你为它们添加容量行。非抢占 CPU 在策略 reserved 之内故事你在池上限制毫核 CPUmilli-CPU再通过策略的 reserved / limit 拆分。非抢占 CPU 需求必须适配每个键的 reserved。池{ name: training, capacity: { gpu: 4, mcpu: 32000 } }策略{ pool: training, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 2, mcpu: 4000 }, limit: { gpu: 4, mcpu: 32000 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count2, preemptibleFalse, ) } ) def fits_reserved_cpu() - None: ...结果分配成功。cpu_count2→mcpu2000 ≤ reserved 4000且gpu合法。非抢占 CPU 超过策略 reserved池{ name: training, capacity: { gpu: 4, mcpu: 32000 } }策略{ pool: training, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 2, mcpu: 4000 }, limit: { gpu: 4, mcpu: 32000 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count8, preemptibleFalse, ) } ) def exceeds_reserved_cpu() - None: ...结果拒绝。cpu_count8→mcpu8000 reserved 4000非抢占工作不能在mcpu上向 limit 借用。带策略mcpu行的可抢占 CPU 突增池与策略与《非抢占 CPU 在策略 reserved 之内》相同池含gpu与mcpu策略同时设置两个键。步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count8, preemptibleTrue, ) } ) def preemptible_cpu_burst() - None: ...结果可以使用mcpu上至 limit 的余量以及池的空闲容量进行分配与 GPU 借用机制类似。池列出mcpu但策略省略它时——可抢占场景故事池限制了总毫核 CPU。策略上没有mcpu时reserved 默认为 0limit 回退为池总量。池{ name: training, capacity: { gpu: 4, mcpu: 8000 } }策略只含gpu{ pool: training, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 2 }, limit: { gpu: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count4, preemptibleTrue, ) } ) def preemptible_with_pool_mcpu() - None: ...结果分配成功或在可行时排队后分配。mcpu4000 ≤ 有效 limit 8000池总量。这印证了核心概念中的回退规则池定义了键但策略省略该键时limit 回退到池总量、reserved 默认为 0见 核心概念。池列出mcpu但策略省略它时——非抢占场景池与策略与上一示例相同。步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count1, preemptibleFalse, ) } ) def non_preemptible_positive_mcpu_zero_reserved() - None: ...结果拒绝。任何正的mcpu请求在 reserved 为 0 时对非抢占工作都失败。修复方法把mcpu加进策略并留足 reserved或者把mcpu从池中移除如果你希望池层面 CPU 完全无界的话。用step_run限制并发步骤数故事你既想要 GPU又想限制该编排器同时运行的步骤数量。每个步骤恒请求 1 个step_run。池{ name: training, capacity: { gpu: 16, step_run: 10 } }策略{ pool: training, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 8, step_run: 4 }, limit: { gpu: 16, step_run: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count2, preemptibleTrue, ) } ) def train() - None: ...结果服务端只在gpu与step_run同时有足够空闲单元时才授予分配。如果 GPU 空闲但step_run槽位全被占用请求会进入队列等待。step_run由服务端为每个步骤自动追加1这是动态流水线并发放宽的天然抓手。pool_resources自定义键自定义键完整配置故事你用pool_resources追踪稀缺的许可证或设备类别如 TensorRT 会话数。池{ name: inference, capacity: { gpu: 8, tensorrt_sessions: 4 } }策略{ pool: inference, component: gpu-step-op, component_type: step_operator, priority: 10, reserved: { gpu: 2, tensorrt_sessions: 2 }, limit: { gpu: 8, tensorrt_sessions: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, pool_resources{tensorrt_sessions: 1}, preemptibleFalse, ) } ) def infer() - None: ...结果当gpu与tensorrt_sessions都满足1 ≤ reserved时分配成功。无界默认值不适用于自定义键——池必须列出它们。注意本示例的策略组件类型是step_operator当步骤使用步骤执行器时资源请求者就是该步骤执行器而非编排器见 对账流程。自定义键在池上但策略缺失故事池容量相同但策略只定义了gpu。池{ name: inference, capacity: { gpu: 8, tensorrt_sessions: 2 } }策略{ pool: inference, component: gpu-step-op, component_type: step_operator, priority: 10, reserved: { gpu: 2 }, limit: { gpu: 8 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, pool_resources{tensorrt_sessions: 1}, preemptibleFalse, ) } ) def infer() - None: ...结果拒绝。策略缺行 →tensorrt_sessions的 reserved 为 0非抢占任务不能请求正数量。修复方法把tensorrt_sessions加入策略或者把步骤标记为可抢占如果允许借用的话。硬性拒绝不排队请求超过池容量池{ name: small, capacity: { gpu: 8 } }策略{ pool: small, component: k8s-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 8 }, limit: { gpu: 8 } }步骤step( settings{ resources: ResourceSettings( gpu_count10, preemptibleTrue, ) } ) def too_big_for_planet() - None: ...结果立即拒绝。10 超出池的gpu总量请求不会进入队列。这类失败对应ResourceRequestStatus.REJECTED状态见 src/zenml/enums.py 中的状态枚举。请求超过策略 limit池放得下池{ name: shared, capacity: { gpu: 8 } }策略{ pool: shared, component: team-a-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 2 }, limit: { gpu: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count6, preemptibleTrue, ) } ) def over_team_limit() - None: ...结果拒绝。6 超出该组件gpu的 limit4即使池里确实有 8 块 GPU。limit 是组件在任何时刻可从池中持有的硬性上限。争用队列与优先级两队同优先级、GPU 不足故事Red 与 Blue 编排器共享一个池策略优先级相同。许多可抢占步骤各要 2 块 GPU池无法同时满足所有人。池{ name: shared, capacity: { gpu: 8 } }策略[ { pool: shared, component: red-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } }, { pool: shared, component: blue-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } } ]步骤两队任一方的典型步骤step( settings{ resources: ResourceSettings( gpu_count2, preemptibleTrue, ) } ) def train() - None: ...结果请求在池队列中等待直到 GPU 释放。同一策略优先级下排序倾向于更早的等待者FIFO 风格。当多个请求都在等待时分配器会优先选择仍能完全落在自己未用 reserved 份额内的请求而不是必须借用的请求——因此拥有预留余量的团队不会被困在已经超借的另一团队后面只要下一次授权可以由该预留份额满足。在更高优先级的等待者出现或回收逻辑触发之前不会发生抢占。更高优先级胜出低优先级可能被抢占故事Sandbox 用可抢占工作突增。Production 策略优先级更高当池满时需要 GPU。池{ name: shared, capacity: { gpu: 8 } }策略[ { pool: shared, component: sandbox-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 4 }, limit: { gpu: 8 } }, { pool: shared, component: prod-orch, component_type: orchestrator, priority: 100, reserved: { gpu: 2 }, limit: { gpu: 8 } } ]Sandbox已持有 6 块 GPU可抢占step( settings{ resources: ResourceSettings( gpu_count6, preemptibleTrue, ) } ) def sandbox_experiment() - None: ...Prod新到达可抢占需要 4 块step( settings{ resources: ResourceSettings( gpu_count4, preemptibleTrue, ) } ) def prod_train() - None: ...结果如果无法在不回收空间的情况下授予 4 块 GPU对账器reconciler可能会抢占 Sandbox 的可抢占运行策略优先级更低让 Prod 继续推进。受害者排序的细节见 How preemption works——其核心规则是只有preemptibleTrue的运行会被停止受害者按策略优先级升序、再按分配时间排序同时回收reclaim逻辑允许拥有未用 reserved 份额的等待者把正在借用容量上的可抢占运行挤走。生产非抢占任务只依赖 reserved 等待故事Prod 使用preemptibleFalse只申请 reserved 范围内的资源。如果同一栈组件上已有其他非抢占任务占用了预留 GPU本步骤不会去借用 Sandbox 的突增容量。池{ name: shared, capacity: { gpu: 8 } }策略与上一示例相同Sandbox 优先级 10Prod 优先级 100Prod 步骤step( settings{ resources: ResourceSettings( gpu_count2, preemptibleFalse, ) } ) def prod_sla_job() - None: ...结果如果prod-orch的 reservedgpu2已被其他非抢占工作占用本步骤在队列中等待。它不会拿走 Sandbox 借用的 GPU。出路提高 Prod 的 reserved、等待其他任务结束或在策略允许时改用可抢占的 Prod 工作。多池与多键请求⚠️关键限制同一栈组件上的多个策略意味着多个池可能都在尝试满足同一个资源请求但只有一个池能胜出。请求中的每个键都必须通过该池的检查ZenML 不会为单个步骤从一个池取gpu、从另一个池取mcpu。整个请求必须在同一个池与策略上完全满足。一个编排器绑定两个池——主池胜出故事你给同一编排器挂接了指向不同池的两个策略。步骤仍然只产生一个资源请求会在每个合格池中入队但最终只有一个池胜出。池[ { name: eu-west-gpu, capacity: { gpu: 16 } }, { name: eu-north-gpu, capacity: { gpu: 16 } } ]策略[ { pool: eu-west-gpu, component: regional-orch, component_type: orchestrator, priority: 20, reserved: { gpu: 8 }, limit: { gpu: 16 } }, { pool: eu-north-gpu, component: regional-orch, component_type: orchestrator, priority: 10, reserved: { gpu: 8 }, limit: { gpu: 16 } } ]步骤step( settings{ resources: ResourceSettings( gpu_count1, preemptibleTrue, ) } ) def train() - None: ...结果服务端先尝试更高策略优先级——先 eu-west 后 eu-north。先授予分配的池拥有本次分配另一条队列条目作为过期项被丢弃。这适合用作主/备或区域性容量方案而不是把单步拆到互不相关的配额上。一个步骤必须在每个池中满足所有键故事资格检查是逐池、针对请求上的所有键进行的。如果某个池缺少步骤需要的键该池视其为零容量——请求在那里不合格。池 A仅 GPU{ name: gpu-only, capacity: { gpu: 8 } }池 B完整组合{ name: gpu-and-trt, capacity: { gpu: 8, tensorrt_sessions: 4 } }步骤step( settings{ resources: ResourceSettings( gpu_count1, pool_resources{tensorrt_sessions: 1}, preemptibleTrue, ) } ) def infer() - None: ...结果只含gpu的池无法满足tensorrt_sessions——该维度在那里为零请求不会在那个池上入队。教训把你关心的每个稀缺有界维度都建模在同一个池上或确保每个候选池对这些键定义相同的键集。下一节展示mcpu与memory_mb的不同之处在一个池上省略它们会让那条路径保持合格即使另一个池严格计量它们。双池高优先级路径计量 CPU/RAM仅 GPU 路径仍然胜出故事一个编排器有两个策略模式同《一个编排器绑定两个池》。池 B 的容量与策略包含mcpu和memory_mbreserved 按小型非抢占任务设计。池 A 只定义gpu不列出mcpu或memory_mb——因此这些维度在池层面无界其策略也不为它们预留。一个非抢占步骤申请 1 块 GPU但 CPU 和内存需求超过池 B 策略允许的范围。高优先级策略池 B无法授予该请求低优先级策略池 A可以因为该路径上请求的 CPU 与内存需求不受配额约束。分配归池 A 所有。池 A仅 GPU——池上无mcpu或memory_mb{ name: gpu-only-fallback, capacity: { gpu: 8 } }池 BGPU 加上计量的 CPU 与内存{ name: metered-cpu-mem, capacity: { gpu: 8, mcpu: 128000, memory_mb: 524288 } }策略同一组件不同优先级——两者都能授予时优先 B[ { pool: metered-cpu-mem, component: k8s-orch, component_type: orchestrator, priority: 100, reserved: { gpu: 4, mcpu: 4000, memory_mb: 8192 }, limit: { gpu: 8, mcpu: 64000, memory_mb: 131072 } }, { pool: gpu-only-fallback, component: k8s-orch, component_type: orchestrator, priority: 50, reserved: { gpu: 4 }, limit: { gpu: 8 } } ]步骤step( settings{ resources: ResourceSettings( gpu_count1, cpu_count8, memory32GiB, preemptibleFalse, ) } ) def train() - None: ...结果请求大约映射为mcpu8000 以及32GiB对应的数万memory_mb。对非抢占工作而言每个键必须 ≤ 你所用路径上策略的 reserved。池 B 的策略只预留mcpu4000 与memory_mb8192该路径无法满足本步骤。池 A 的策略没有mcpu或memory_mb行由于这些键在池上缺席它们不会被当作零容量因此步骤仅凭gpu就保持合格。对账器从池 A 分配并丢弃池 B 的竞争队列行。如果你希望大型非抢占任务留在计量池上请提高池 B 的mcpu与memory_mb的 reserved以及容量或在ResourceSettings中降低需求——否则仅 GPU 的策略会成为重 CPU/RAM 请求的逃生通道。从源码看状态机与 CLI 速查上述所有结果最终都落在资源请求的状态上。ResourceRequestStatus枚举定义了完整生命周期pending排队、allocated已分配、preempting/preempted抢占中/已抢占、cancelled取消、rejected拒绝、released释放见 src/zenml/enums.py。步骤启动器会以退避方式轮询资源请求直到被分配若请求被拒绝、抢占或取消客户端会向用户暴露错误见 对账流程。CLI 命令族在 src/zenml/cli/resource_pool.py 中实现池的容量以 JSON/YAML 字符串解析为整数值# 池创建 / 列出 / 查看 / 更新 / 删除 zenml resource-pool create training-gpus \ --capacity {gpu: 8, step_run: 32} \ --description Shared training GPUs for the workspace zenml resource-pool list zenml resource-pool describe training-gpus zenml resource-pool update training-gpus --capacity {gpu: 4} zenml resource-pool delete training-gpus --yes # 策略把栈组件挂接到池 zenml resource-pool attach-policy training-gpus my-k8s-orch \ --priority 10 \ --reserved {gpu: 2} \ --limit {gpu: 4} zenml resource-pool attach-policy training-gpus my-remote-operator \ --component-type step_operator \ --priority 5 \ --reserved {gpu: 2} \ --limit {gpu: 4} zenml resource-pool list-policies training-gpus zenml resource-pool list-policies --component my-k8s-orch zenml resource-pool detach-policy training-gpus my-k8s-orch # 请求查看与清理 zenml resource-request list zenml resource-request describe 01234567-89ab-cdef-0123-456789abcdef zenml resource-request delete 01234567-89ab-cdef-0123-456789abcdef zenml resource-pool requests training-gpus --view queued zenml resource-pool requests training-gpus --view active zenml resource-pool requests training-gpus --view all完整命令族的实现细节含create/update/attach-policy等各子命令的参数解析与校验可继续查阅 src/zenml/cli/resource_pool.py对应的数据模型池、策略、请求的响应结构位于 src/zenml/models/v2/core/resource_pool.py、src/zenml/models/v2/core/resource_pool_subject_policy.py 与 src/zenml/models/v2/core/resource_request.py。参见资源池总览——功能定位与典型使用场景核心概念——池、策略、请求的精确定义How preemption works——抢占的受害者排序步骤与流水线配置——ResourceSettings的完整字段参考OSS 文档【免费下载链接】zenmlZenML : One AI Platform from Pipelines to Agents. https://zenml.io.项目地址: https://gitcode.com/GitHub_Trending/ze/zenml创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表