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

函数主体

类似于管道函数主体,您可以实例化组件,创建 条件,使用函数签名中的输入参数,并在组件之间显式指定依赖关系。在上面的示例中,递归函数内部创建了一个条件,并在条件内部创建了两个组件 op_aop_b

在管道函数中调用递归函数

您可以将管道/组件的输出传递给递归函数,并使用 after() 函数显式指定依赖关系,这类似于 ContainerOp。在上面的示例中,管道中定义的 op_a 的输出被传递给递归函数,并且 task_factory_c 组件被指定依赖于 graph_op_a。递归函数也可以被显式指定依赖于 ContainerOps。例如,graph_op_a 在管道中依赖于 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 无法访问递归函数的输出。
  • 一个已知的 问题 是,当函数体内有多个递归函数调用时,递归无法正常工作。

下一步

反馈

此页面有帮助吗?