控制流 - 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),该运算符保留了谓词、true 函数和 false 函数。这为编写和部署模型提供了极大的灵活性,这些模型可以根据张量运算的输入或中间输出的值或形状来更改模型架构。
警告
torch.cond 是 PyTorch 中的一个原型功能。它对输入和输出类型的支持有限,目前不支持训练。请期待未来 PyTorch 版本中更稳定的实现。阅读更多关于功能分类的信息,请访问: https://pytorch.ac.cn/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):
"""
A basic usage of cond based on dynamic shape predicate.
"""
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()
我们可以 eager 模式运行模型,并预期结果会根据输入形状而变化
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 <lambda>(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 <lambda>(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):
"""
A basic usage of cond based on data dependent predicate.
"""
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 <lambda>(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 <lambda>(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 有几个有用的不变性
- 对于谓词
谓词的动态性被保留(例如,上面示例中显示的 gt)
如果用户程序中的谓词是常量(例如,一个 python bool 常量),则运算符的 pred 将是一个常量。
- 对于分支
输入和输出签名将是一个扁平化的元组。
它们是 torch.fx.GraphModule。
原始函数中的闭包变为显式输入。没有闭包。
不允许对输入或全局变量进行修改。
- 对于操作数
它也将是一个扁平化的元组。
用户程序中 torch.cond 的嵌套变为嵌套的图模块。
API 参考¶
- torch._higher_order_ops.cond.cond(pred, true_fn, false_fn, operands=())[源代码]¶
有条件地应用 true_fn 或 false_fn。
警告
torch.cond 是 PyTorch 中的一个原型功能。它对输入和输出类型的支持有限,目前不支持训练。请期待未来 PyTorch 版本中更稳定的实现。阅读更多关于功能分类的信息,请访问: https://pytorch.ac.cn/blog/pytorch-feature-classification-changes/#prototype
cond 是结构化控制流运算符。也就是说,它类似于 Python 的 if 语句,但对 true_fn、false_fn 和 operands 有限制,使其可以使用 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)
- 参数
pred (Union[bool, torch.Tensor]) – 一个布尔表达式或一个单元素张量,指示要应用哪个分支函数。
true_fn (Callable) – 一个可调用函数 (a -> b),它在被追踪的作用域内。
false_fn (Callable) – 一个可调用函数 (a -> b),它在被追踪的作用域内。true 分支和 false 分支必须具有一致的输入和输出,这意味着输入必须相同,输出必须具有相同的类型和形状。
operands (Tuple of possibly nested dict/list/tuple of torch.Tensor) – true/false 函数的输入元组。如果 true_fn/false_fn 不需要输入,则可以为空。默认为 ()。
- 返回类型
示例
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,))
- 限制
条件语句(又名 pred)必须满足以下约束之一
它是一个 torch.Tensor,只有一个元素,并且 dtype 为 torch.bool
它是一个布尔表达式,例如 x.shape[0] > 10 或 x.dim() > 1 and x.shape[1] > 10
分支函数(又名 true_fn/false_fn)必须满足以下所有约束
函数签名必须与操作数匹配。
函数必须返回具有相同元数据的张量,例如形状、dtype 等。
函数不能对输入或全局变量进行就地修改。(注意:允许在分支中使用就地张量操作,例如 add_ 用于中间结果)
警告
临时限制
分支的 输出 必须是 单个张量。未来将支持张量的 Pytree。