TVM学习笔记二.relay IR介绍

relay是一种功能多样的编程语言,用于机器学习系统表达的中间表示。relay支持代数数据类型,闭包,控制流和递归,从而可以直接表示比基于计算图的IR更复杂的模型。relay还包括一种使用类型关系的依赖类型的形式,来处理对参数形状有复杂的要求的操作符的形状分析。relay在设计上是可扩展的,这使得机器学习的开发者可以很容易地开发新的大型程序转换和优化。

下面分别介绍Relay中的语法,类型系统,代数数据类型和运算符。

一.Relay表达式

Relay IR是一个纯粹面向表达式的表达语言。下面描述Relay中不同的表达式并给出其语义的定义细节。

为了比较Relay和传统的基于图的中间表示(DAG),数据流和控制流是Relay必须要考虑的,在编写表示转换时,只影响数据流的情况可以看作是传统的计算图。

数据流片段涵盖了不涉及控制流的一组Relay表达式,例如,以下部分仅包含数据的计算:

  • 变量
  • 三元组构建和映射
  • Let bindings
  • Graph Bindings
  • 操作符调用和抽象语法树构造器

控制流表达式允许计算图的拓扑根据先前的计算结果进行改变。Relay的控制片段有以下结构:

  • if-then-else表达式
  • ADT匹配表达式
  • 递归调用

从计算图的角度看,一个函数是一个子图,函数调用嵌入子图,用相应的名称代替子图中的变量。如果函数的主体只要使用数据流构造吗,则对该函数的调用在数据流片段中;相反,如果函数的主体包含控制流,则对该函数的调用不属于数据流片段。

1.1 Variables

在LLVM的启发下,Relay显式地区分了AST和文本格式中的局部变量和全局变量。在文本格式中,全局变量和局部变量通过前缀或者标记来区分。全局变量以@为前缀,局部变量以%为前缀.

这种明确的定义区分使得某些优化很容易被实施,例如,内联全局定义无需进行分析,只需要替换定义即可。

1.1.1 Global Variable

全局变量用@进行标记,如"@global"。全局标识符始终引用在全局可见环境(称之为模块)中定义,全局变量必须是唯一的。

1.1.2 Local Variable

局部变量以%进行标记,如"%local"。局部变量总是函数的形参或者let绑定的一个变量,并且将范围分别限制为出现它的函数或它绑定的let表达式。

下面举例说明:

let %a = 1;
let %b = 2 * %a;  // %b = 2
let %a = %a + %a; // %a = 2. %a is shadowed
%a + %b           // has value 2 + 2 = 4

可以发现%a定义了两次,与大多数编程语言一样,这是合法的;在第二个let表达式的范围中,变量%a被“阴影化”,这意味着内部范围中对%a的所有引用均引用后面的定义,而外部范围中对%a的引用继续引用第一个定义。

1.2 Functions

Relay中函数的作用类似于其它编程语言中的程序或函数,并且可以推广用于命名子图的概念。函数是Relay中的第一类,这意味着它们是像变量、常量和元组一样的表达式。此外,Relay中的函数是高阶的,也就是说函数可以作为参数传递给函数或者由函数返回,因为函数表达式计算为闭包(Closures),它是类似于张量和元组的值。

1.3 Syntax

语句(Syntax)的定义至少由关键字fn、一组空参数和{}所包含的主体个表达式(Expr)组成。

fn() { body }

语句也可以包含任意数目的参数。例如add运算符的简单函数:

fn(%x, %y) { add(%x, %y) }

在函数体中,参数是局部变量,就像let绑定表达式中的变量一样。

我们还可以对函数显式类型进行标注,例如,我们可以限制上面的额add函数只在某些类型上合法,限制后该函数仅接受Tensor[(10, 10), float32]类型的参数,并返回Tensor[(10, 10), float32]类型的值。函数的参数是局部变量和带有类型标注,写为%x:T.

fn(%x : Tensor[(10, 10), float32], %y : Tensor[(10, 10), float32])
           -> Tensor[(10, 10), float32] {
    add(%x, %y)
}

当类型信息缺省时,Relay会尝试为用户推断最通用的类型,这个属性被称为泛化,Relay试图将最一般的类型分配给参数,并根据函数体和调用返回类型。

递归函数表达式可以使用let绑定:

let %fact = fn(%x : Tensor[(10, 10), float32]) -> Tensor[(10, 10), float32] {
    if (%x == Constant(0, (10, 10), float32)) {
        Constant(1, (10, 10), float32)
    } else {
        %x * %fact(%x - Constant(1, (10, 10), float32))
    }
};
%fact(Constant(10, (10, 10), float32))

1.4 Closures

函数表达式的结算为闭包(Closures),闭包是表示为一对局部环境(存储在函数体之外定义的所有变量的值)和函数体本身的值。例如在下面的示例中,最后的结果将是零值得张量,因为%f存储的值%x在指针处%f被定义了。

let %g = fn() {
  let %x = Constant(0, (10, 10), float32);
  // %x is a free variable in the below function
  fn(%y) { %y * %x }
};
// the %x in %g's body is not in scope anymore
// %f is a closure where %x maps to Constant(0, (10, 10), float32)
let %f = %g();
let %x = Constant(1, (10, 10), float32);
%f(%x) // evaluates to Constant(0, (10, 10), float32)

1.5 Polymorphism and Type Relations

函数可以被赋予一组类型参数,这些参数可以在调用站点替换特定类型。具有类型参数的函数是类型多态;他们的返回类型或它们接受的参数类型可以根据在调用时给定的类型参数不同而有所不同。

类型参数是按种类分类的,并且只能出现在类型签名合适的部分(例如,类型参数的类型中。shape类型的参数只能出现在张量类型中期望出现shape的位置。

例如,可以为任何Relay类型定义一个多态标识函数,如下:

fn(%x : t) -> t { %x }

以下定义也是多态的,但其参数被限制为张量类型:

fn(%x : Tensor[s, bt]) { %x }

注意:返回类型是忽略的,因为它可以被推断出来。

文本格式中还不支持“Where”语法。

一个函数也可能受到一种或多种类型关系的制约,例如在下列情况下:

fn(%x, %y) where Broadcast { add(%x, %y) }

%x和%y返回类型受Broadcast关系约束,这三个必须为张量,并且它们的形状遵循元素的广播关系。和运算法一样,关系的定义对Relay并不透明,而是在C++或python中实现的。

你可能感兴趣的:(TVM,机器学习,tvm)