Shortcuts

控制流程 - Cond

torch.cond 是一个结构化控制流操作符。它可以用于指定类似 if-else 的控制流,并且可以逻辑上视为如下实现。

def cond(
    pred: Union[bool, torch.Tensor],
    true_fn: Callable,
    false_fn: Callable,
    operands: Tuple[torch.Tensor]
):
    if pred:
        return true_fn(*operands)
    else:
        return false_fn(*operands)

它的独特之处在于其表达数据依赖的控制流的能力:它降低为一个条件运算符(torch.ops.higher_order.cond),该运算符保留了谓词、真函数和假函数。这为根据张量操作的形状输入或中间输出来改变模型架构的编写和部署模型提供了极大的灵活性。

警告

torch.cond 是 PyTorch 中的一个原型功能。它对输入和输出类型的支持有限,并且目前不支持训练。请期待未来版本的 PyTorch 中更稳定的实现。 了解更多关于功能分类的信息:https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype

示例

下面是一个使用cond根据输入形状进行分支的示例:

import torch

def true_fn(x: torch.Tensor):
    return x.cos() + x.sin()

def false_fn(x: torch.Tensor):
    return x.sin()

class DynamicShapeCondPredicate(torch.nn.Module):
    """
    基于动态形状谓词的cond的基本用法。
    """

    def __init__(self):
        super().__init__()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        def true_fn(x: torch.Tensor):
            return x.cos()

        def false_fn(x: torch.Tensor):
            return x.sin()

        return torch.cond(x.shape[0] > 4, true_fn, false_fn, (x,))

dyn_shape_mod = DynamicShapeCondPredicate()

我们可以急切地运行模型,并期望结果根据输入形状而变化:

inp = torch.randn(3)
inp2 = torch.randn(5)
assert torch.equal(dyn_shape_mod(inp), false_fn(inp))
assert torch.equal(dyn_shape_mod(inp2), true_fn(inp2))

我们可以导出模型以进行进一步的转换和部署:

inp = torch.randn(4, 3)
dim_batch = torch.export.Dim("batch", min=2)
ep = torch.export.export(DynamicShapeCondPredicate(), (inp,), {}, dynamic_shapes={"x": {0: dim_batch}})
print(ep)

这为我们提供了一个导出的程序,如下所示:

class GraphModule(torch.nn.Module):
    def forward(self, arg0_1: f32[s0, 3]):
        sym_size: Sym(s0) = torch.ops.aten.sym_size.int(arg0_1, 0)
        gt: Sym(s0 > 4) = sym_size > 4;  sym_size = None
        true_graph_0 = self.true_graph_0
        false_graph_0 = self.false_graph_0
        conditional: f32[s0, 3] = torch.ops.higher_order.cond(gt, true_graph_0, false_graph_0, [arg0_1]);  gt = true_graph_0 = false_graph_0 = arg0_1 = None
        return (conditional,)

    class (torch.nn.Module):
        def forward(self, arg0_1: f32[s0, 3]):
            cos: f32[s0, 3] = torch.ops.aten.cos.default(arg0_1)
            sin: f32[s0, 3] = torch.ops.aten.sin.default(arg0_1);  arg0_1 = None
            add: f32[s0, 3] = torch.ops.aten.add.Tensor(cos, sin);  cos = sin = None
            return add

    class (torch.nn.Module):
        def forward(self, arg0_1: f32[s0, 3]):
            sin: f32[s0, 3] = torch.ops.aten.sin.default(arg0_1);  arg0_1 = None
            return sin

请注意,torch.cond 被降低为 torch.ops.higher_order.cond,其谓词变为输入形状上的符号表达式, 并且分支函数变为顶级图模块的两个子图属性。

这里是另一个展示如何表达数据依赖控制流的示例:

class DataDependentCondPredicate(torch.nn.Module):
    """
    基于数据依赖谓词的cond的基本用法。
    """
    def __init__(self):
        super().__init__()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return torch.cond(x.sum() > 4.0, true_fn, false_fn, (x,))

导出的程序我们得到后:

class GraphModule(torch.nn.Module):
    def forward(self, arg0_1: f32[s0, 3]):
        sum_1: f32[] = torch.ops.aten.sum.default(arg0_1)
        gt: b8[] = torch.ops.aten.gt.Scalar(sum_1, 4.0);  sum_1 = None

        true_graph_0 = self.true_graph_0
        false_graph_0 = self.false_graph_0
        conditional: f32[s0, 3] = torch.ops.higher_order.cond(gt, true_graph_0, false_graph_0, [arg0_1]);  gt = true_graph_0 = false_graph_0 = arg0_1 = None
        return (conditional,)

    class (torch.nn.Module):
        def forward(self, arg0_1: f32[s0, 3]):
            cos: f32[s0, 3] = torch.ops.aten.cos.default(arg0_1)
            sin: f32[s0, 3] = torch.ops.aten.sin.default(arg0_1);  arg0_1 = None
            add: f32[s0, 3] = torch.ops.aten.add.Tensor(cos, sin);  cos = sin = None
            return add

    class (torch.nn.Module):
        def forward(self, arg0_1: f32[s0, 3]):
            sin: f32[s0, 3] = torch.ops.aten.sin.default(arg0_1);  arg0_1 = None
            return sin

torch.ops.higher_order.cond 的不变性

对于torch.ops.higher_order.cond,有几个有用的不变量:

  • For predicate:
    • 谓词的动态性得以保留(例如,上例中显示的gt

    • 如果用户程序中的谓词是常量(例如Python布尔常量),则操作符的pred将是一个常量。

  • For branches:
    • 输入和输出的签名将是一个扁平化的元组。

    • 它们是 torch.fx.GraphModule

    • 原始函数中的闭包变为显式输入。没有闭包。

    • 不允许对输入或全局变量进行突变。

  • For operands:
    • 它也将是一个扁平的元组。

  • 在用户程序中嵌套torch.cond会变成嵌套的图模块。

API参考

torch._higher_order_ops.cond.cond(pred, true_fn, false_fn, operands)

有条件地应用true_fnfalse_fn

警告

torch.cond 是 PyTorch 中的一个原型功能。它对输入和输出类型的支持有限,并且目前不支持训练。请期待未来版本的 PyTorch 中更稳定的实现。 了解更多关于功能分类的信息:https://pytorch.org/blog/pytorch-feature-classification-changes/#prototype

cond 是一个结构化的控制流操作符。也就是说,它类似于 Python 的 if 语句, 但对 true_fnfalse_fnoperands 有特定的限制,使其能够 使用 torch.compile 和 torch.export 进行捕获。

假设 cond 的参数约束得到满足,cond 等价于以下内容:

def cond(pred, true_branch, false_branch, operands):
    if pred:
        return true_branch(*operands)
    else:
        return false_branch(*operands)
Parameters
  • pred (Union[bool, torch.Tensor]) – 一个布尔表达式或一个包含单个元素的张量,指示应用哪个分支函数。

  • true_fn (可调用函数) – 一个在正在追踪的范围内可调用的函数 (a -> b)。

  • false_fn (可调用函数) – 一个在正在追踪的范围内可调用的函数 (a -> b)。真分支和假分支必须具有一致的输入和输出,这意味着输入必须相同,输出必须具有相同的类型和形状。

  • 操作数 (元组可能嵌套的字典/列表/元组torch.Tensor) – 输入到真/假函数的元组。

示例:

def true_fn(x: torch.Tensor):
    return x.cos()
def false_fn(x: torch.Tensor):
    return x.sin()
return cond(x.shape[0] > 4, true_fn, false_fn, (x,))
Restrictions:
  • 条件语句(又名 pred)必须满足以下约束之一:

    • 它是一个只有一个元素且数据类型为torch.bool的torch.Tensor

    • 这是一个布尔表达式,例如 x.shape[0] > 10x.dim() > 1 and x.shape[1] > 10

  • 分支函数(又名 true_fn/false_fn)必须满足以下所有约束条件:

    • 函数签名必须与操作数匹配。

    • 该函数必须返回具有相同元数据的张量,例如形状、数据类型等。

    • 函数不能对输入或全局变量进行原地修改。 (注意:对于中间结果,允许在分支中使用诸如 add_ 的原地张量操作)

警告

时间限制:

  • cond 目前仅支持推理。自动微分将在未来得到支持。

  • 分支的输出必须是一个单一的Tensor。未来将支持张量的Pytree。

优云智算