DSL 递归

在 DSL 中编写递归函数

本页面描述了如何在 Kubeflow Pipelines SDK 提供的领域特定语言 (DSL) 中编写递归函数。

动机

递归是几乎所有语言都支持的一项功能,用于简洁地表达复杂的语义。在机器学习工作流中,递归对于启用多轮训练、迭代模型分析和超参数调优等功能尤为重要。递归支持也涵盖了循环功能,因为它允许基于动态条件执行和退出相同的代码块。

如何编写递归函数

装饰器

使用 kfp.dsl.graph_component 装饰器修饰递归函数,如下所示。此装饰器不需要任何参数。

import kfp.dsl as dsl
@dsl.graph_component
def graph_component_a(input_x):
  with dsl.Condition(input_x == 'value_x'):
    op_a = task_factory_a(input_x)
    op_b = task_factory_b().after(op_a)
    graph_component_a(op_b.output)
    
@dsl.pipeline(
  name='pipeline',
  description='shows how to use the recursion.'
)
def pipeline():
  op_a = task_factory_a()
  op_b = task_factory_b()
  graph_op_a = graph_component_a(op_a.output)
  graph_op_a.after(op_b)
  task_factory_c(op_a.output).after(graph_op_a)

函数签名

将函数签名定义为标准的 Python 函数。输入参数是 PipelineParams

函数体

与 pipeline 函数体类似,你可以实例化组件,创建条件,使用函数签名中的输入参数,并明确指定组件之间的依赖关系。在上面的示例中,递归函数内部创建了一个条件,并且在条件内部创建了两个组件 op_aop_b

在 pipeline 函数中调用递归函数

你可以将 pipeline/组件的输出传递给递归函数,并使用 after() 函数明确指定依赖关系,类似于 ContainerOp。在上面的示例中,pipeline 中定义的 op_a 的输出被传递给递归函数,并且 task_factory_c 组件被指定依赖于 graph_op_a。递归函数也可以明确指定依赖于 ContainerOps。例如,graph_op_a 在 pipeline 中依赖于 op_b

更多示例

这里是另一个示例,其中递归函数调用位于函数体的末尾,类似于 do-while 循环。

import kfp.dsl as dsl
@dsl.graph_component
def graph_component_a(input_x):
  op_a = task_factory_a(input_x)
  op_b = task_factory_b().after(op_a)
  with dsl.Condition(op_b.output == 'value_x'):
    graph_component_a(op_b.output)
 
@dsl.pipeline(
  name='pipeline',
  description='shows how to use the recursion.'
)
def pipeline():
  op_a = task_factory_a()
  op_b = task_factory_b()
  graph_op_a = graph_component_a(op_a.output)
  graph_op_a.after(op_b)
  task_factory_c(op_a.output).after(graph_op_a)

限制

  • 类型检查对递归函数不起作用。换句话说,注解到递归函数签名的类型信息将不会被检查。
  • 由于递归函数的输出无法动态解析,下游的 ContainerOps 无法访问递归函数的输出。
  • 一个已知的 问题 是,当函数体中有多个递归函数调用时,递归不起作用。

下一步

反馈

此页面有帮助吗?