想象一家大型餐厅的后厨。客人点的是精致的创意菜——“红酒炖牛腩”“芒果千层蛋糕”,这些写在菜单上的菜谱,就像编译器里的“高层面板”。但后厨里的帮厨只认一套最基础的动作:切、煮、炸、烤、称量。无论菜谱多花哨,最终都得翻译成这些基础动作,炉火才能执行。MLIR 的“标准方言”就是那张全行业统一的基础动作清单:它规定了内置的算术运算、比较逻辑、张量操作和基础类型系统,是所有高层方言与底层硬件之间的通用语言。任何自定义方言,都要经过“降级”(Lowering),一步步把花哨菜谱拆成白纸黑字的基础动作。本文就用这个“后厨”视角,拆解标准方言的定位、类型系统、核心操作,以及它在编译流水线中承上启下的角色。

岗位关联

理解 MLIR 标准方言,是进入现代 AI 编译器(Torch-MLIR、Triton 等)的钥匙:它决定你能否读懂计算图从“高层算子”到“机器码”的全过程,也是为 AI 芯片编写编译后端的必备功底。

目录
  1. 可视化演示
  2. 标准方言定位:后厨的基础动作清单
  3. 基础类型系统:给食材贴标签
  4. 算术运算指令:后厨的刀工基础
  5. 比较逻辑运算:尝味道与做选择
  6. 张量操作:重新摆盘的艺术
  7. Lowering机制:菜谱拆解成步骤
  8. 流水线角色:后厨的完整流程
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示MLIR Lowering降级流水线、标准方言操作集及张量内存布局,支持敏感度调节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

MLIR Lowering降级流水线 蓝色高层框架算子如torch.matmul 绿色标准方言底层算子arith.mulf High IR → Std Dialect → LLVM IR → Machine 逐步降级从高层抽象到低层机器指令

标准方言定位:后厨的基础动作清单

餐厅后厨墙上贴着两张表:一张是当季创意菜谱,天天更新;另一张是基础动作清单——切、煮、炸、烤、调味,十年不变,所有帮厨都要背熟。MLIR 的世界里,高层面板就是那份创意菜谱,而标准方言就是那张基础动作清单。它是框架内置的“公地”,不归任何一家自定义方言所有。

标准方言(Standard Dialect)是 MLIR 自带的基础操作与类型集合。现代 MLIR 把它细分为 arith(算术)、tensor、memref 等子方言,但思路一脉相承:无论上层方言多花哨,最终都要能翻译成这套基础操作。它解决了“不同方言之间如何对话”的问题。

从角色看,标准方言是“承上启下”的枢纽。向上,它为新方言提供语义参照系,新算子可以先用标准方言实现再逐步优化;向下,它有经过千锤百炼的 LLVM 降级路径。新方言作者只需把自己算子重写为标准方言,就搭上了通往机器码的高速路。

就像后厨只要把创意菜谱翻译成基础动作清单,就再也不用担心炉火怎么烧——标准方言把“方言到机器码”最难的一段变成了公共成熟通路。打牢这张清单的底子,是理解编译流水线的第一步。

ToyDialect  →Lowering  StdDialect  →Lowering  LLVM IR\text{ToyDialect} \;\xrightarrow{\text{Lowering}}\; \text{StdDialect} \;\xrightarrow{\text{Lowering}}\; \text{LLVM IR}
这条链表示:任何自定义方言都可以先落到标准方言,再借标准方言的成熟后端到达 LLVM,逐层降低抽象。
  1. Toy 方言代表用户自定义的高层方言,描述贴合业务的复合操作,如矩阵乘、卷积。
    ToyDialect\text{ToyDialect}
  2. Lowering 箭头方言转换过程,把高层操作改写为低层操作的序列,这是第一次降级。
    →Lowering\xrightarrow{\text{Lowering}}
  3. 标准方言降级的目标,由算术、比较、张量操作与基础类型构成,是所有方言的公共底层。
    StdDialect\text{StdDialect}
  4. 到达 LLVM IR第二次降级,把标准方言逐条映射为 LLVM 指令,最终交给后端生成机器码。
    LLVM IR\text{LLVM IR}

基础类型系统:给食材贴标签

帮厨最怕什么?把盐当成糖。为了避免事故,后厨给每种食材贴统一标签:面粉、糖、盐、水。MLIR 的类型系统就是标准方言的“食材标签”,它决定了每个值是什么类型、能参与什么运算。

标准方言提供一组基础类型。index 表示索引和位置(像盘子的编号);i32、f64 表示整数与浮点标量;tensor 表示不可变的多维数据结构;memref 表示可读写内存区域的引用。标签附着在每个 SSA 值上,像食材标签一样跟着数据走。

类型的严谨性是编译器的底气。tensor<2x4xf32> 读作“形状为 2 行 4 列、元素为 32 位浮点的张量”。编译器靠这些标签做一致性检查;memref 还会记录内存布局与地址空间。与脚本语言不同,MLIR 在编译期就确定每个操作的类型,这是优化与代码生成的前提。

标签系统让整个后厨有条不紊:打荷的看到 f32 就不会把整数操作塞给它,传菜的看到 memref 就知道这块食材占着灶台内存。理解了类型,就掌握了标准方言的语法底层。

tensor⟨2×4×f32⟩\text{tensor}\langle 2 \times 4 \times \mathrm{f32}\rangle
张量类型由“形状列表”和“元素类型”两部分构成,形状各维度乘积等于元素个数。
  1. tensor 关键字声明这是张量类型,表示不可变的多维数据容器。
    tensor\text{tensor}
  2. 形状列表尖括号内 2 × 4 表示两个维度的长度,总共 8 个元素。
    2×42 \times 4
  3. 元素类型 f32每个元素为 32 位浮点数,决定了数值精度与运算规则。
    f32\mathrm{f32}

算术运算指令:后厨的刀工基础

刀工是后厨的根基——切丁、切片、剁泥、拍蒜。没有这些基础动作,任何精致摆盘都无从谈起。标准方言的算术指令就是这样的“刀工”。

MLIR 把算术操作放在 arith 系列:addi、subi、muli 是整数加减乘,addf、subf、mulf 是浮点版本;divsi/divui 表示有符号/无符号整数除法,divf 是浮点除法,remi 求余数。后缀 i 代表 integer,f 代表 float——这就是命名里的“刀型标签”。

算术指令最重要的约束是类型一致性:arith.addi 要求两个操作数和结果同为整数类型,比如 (i32, i32) → i32;把 i32 和 f32 直接相加是违法的,必须先做类型转换。这种严格约束让编译器在编译期就能捕获大量“食材放错位置”的 bug。整数除法还要区分有符号与无符号,它们决定负数除法时的舍入方向。

和刀工一样,算术指令看似简单,却是优化最常下手的地方:编译器可以把 a × 2 改成 a + a,把 a × 1 直接删掉。刀法越标准,优化的空间越透明。

arith.addi(lhs,rhs):  (i32,i32)→i32\text{arith.addi}(\text{lhs}, \text{rhs}) : \; (\mathrm{i32}, \mathrm{i32}) \to \mathrm{i32}
加法的类型签名:输入两个 32 位整数,产出一个 32 位整数,三者类型必须一致。
  1. 操作名 arith.addi标准方言的整数加法指令,i 后缀表示整数操作。
    arith.addi\text{arith.addi}
  2. 操作数 lhs, rhs两个输入操作数,必须是同一整数类型,否则非法。
    (lhs,rhs)(\text{lhs}, \text{rhs})
  3. 类型签名括号内是输入类型,箭头右侧是结果类型,三者同属 i32。
    (i32,i32)→i32(\mathrm{i32}, \mathrm{i32}) \to \mathrm{i32}

比较逻辑运算:尝味道与做选择

大厨的日常是“尝一口,再决定”:汤咸了加水,淡了加盐。这个“判断 + 选择”的过程,对应标准方言里的比较与逻辑指令。

cmpi 是比较指令,带一个“谓词”参数:eq(相等)、ne(不等)、slt(有符号小于)、sgt(有符号大于)、ult(无符号小于)等,输出是 i1 布尔值。逻辑层面有 and、or、xor 组合布尔值;select 指令则像三元表达式,按布尔条件从两个值中挑一个。

为什么需要这些指令?因为 SSA 形式里值一旦定义就不可变,没有传统语言那种“if 语句改变量”的写法,控制流通过分支加 select 实现数据选择。有符号比较与无符号比较在负数场景下结果完全不同:0xFFFFFFFF 作为有符号数是 -1,作为无符号数是 4294967295,选错谓词程序就错了。

尝味和选择相辅相成:cmpi 尝出咸淡,select 决定加盐还是加水。标准方言用这套小工具,在低层搭建起高层面板里"if-else"的样子。

cmpi  slt,  lhs,  rhs:  (i32,i32)→i1\text{cmpi} \; \text{slt}, \; \text{lhs}, \; \text{rhs} : \; (\mathrm{i32}, \mathrm{i32}) \to \mathrm{i1}
这是“尝味”指令:按“有符号小于”规则比较两个整数,输出一个 0 或 1 的布尔。
  1. cmpi 与谓词 sltcmpi 发起比较,slt 指定“有符号小于”这一判断规则。
    cmpi  slt\text{cmpi} \; \text{slt}
  2. 两个操作数lhs 与 rhs 为同类型整数,参与大小比较。
    lhs,rhs:(i32,i32)\text{lhs}, \text{rhs} : (\mathrm{i32}, \mathrm{i32})
  3. 结果 i1输出单比特布尔,1 表示条件成立,0 表示不成立。
    →i1\to \mathrm{i1}

张量操作:重新摆盘的艺术

一条烤盘上摆 2 行 4 列共 8 块饼干,后厨为省烤箱空间,把它们重新排成 1 行 8 个。张量操作就是这种“重新摆盘”的技术。

标准方言的张量操作关心“结构”而非“数值数学”:tensor.from_elements 把一组标量组装成张量;tensor.reshape、expand_shape、collapse_shape 改变形状;tensor.extract / tensor.insert 按索引访问和写入元素。真正的矩阵乘法等高密度数学,属于 linalg、math 这类更高语义的方言。

形状变换遵循“元素数守恒”:tensor<2×4×f32> 重排成 tensor<8×f32> 只改形状不改数据个数,所以 reshape 往往只是“换个视角”。当 tensor.extract 的索引全是编译期常量时,求值可以立即折叠成一次常量读取。这一层结构操作,是后续缓冲区分配(bufferization)的铺垫。

摆盘技巧的价值在于为“上菜”做准备:张量结构理顺了,才能谈分配内存、优化访存。一盘菜怎么摆,决定了客人(硬件)吃得顺不顺。

tensor.collapse_shape:  tensor⟨2×4×f32⟩→tensor⟨8×f32⟩\text{tensor.collapse\_shape} : \; \text{tensor}\langle 2 \times 4 \times \mathrm{f32}\rangle \to \text{tensor}\langle 8 \times \mathrm{f32}\rangle
形状折叠:把二维张量摊平成一维,元素总数保持不变,仅改变“怎么看这盘菜”。
  1. 操作名 collapse_shape折叠形状操作,属于张量结构变换家族。
    tensor.collapse_shape\text{tensor.collapse\_shape}
  2. 输入形状2 × 4 的二维张量,共 8 个元素。
    tensor⟨2×4×f32⟩\text{tensor}\langle 2 \times 4 \times \mathrm{f32}\rangle
  3. 输出形状摊平为长度 8 的一维张量,元素类型保持不变。
    tensor⟨8×f32⟩\text{tensor}\langle 8 \times \mathrm{f32}\rangle

Lowering机制:菜谱拆解成步骤

主厨收到“芒果千层蛋糕”的订单,不会把蛋糕直接扔进烤箱,而是拆成摊皮、打奶油、叠层、冷藏一系列基础步骤。这个“菜谱拆解”就是编译器里的 Lowering(降级)。

MLIR 提供 DialectConversion 方言转换框架:开发者为高层面板的操作编写重写模式(rewrite pattern),把复合操作改写为标准方言操作序列。转换由模式匹配驱动:左边匹配到某种操作形态,右边生成目标方言的操作。

降级通常是“渐进式”的:toy 方言先降到 arith、tensor、memref 等标准方言,再降到 LLVM。每次只降低一层抽象,既保留中间优化机会,又便于分步调试。标准方言恰好是这条链上最高的“通用站”——它通往 LLVM 的路径是社区反复打磨过的。

拆解菜谱的学问在于“拆到什么程度”:拆太粗,炉火看不懂;拆太细,浪费灶台。渐进降级让每一步都恰到好处,而标准方言就是那条被验证过千万次的“基准拆法”。

toy.multiply(a,b)  ⇒  arith.mulf(a,b)\text{toy.multiply}(a, b) \;\Rightarrow\; \text{arith.mulf}(a, b)
一个最简单的重写模式:把自定义方言的乘法算子改写为标准方言的浮点乘法。
  1. 左侧被匹配模式toy.multiply 是高层面板中一个自定义乘法算子。
    toy.multiply(a,b)\text{toy.multiply}(a, b)
  2. 重写箭头表示模式匹配成功后的替换,对应编译器里的一条 rewrite rule。
    ⇒\Rightarrow
  3. 右侧生成操作arith.mulf 是标准方言浮点乘法,语义完全等价。
    arith.mulf(a,b)\text{arith.mulf}(a, b)

流水线角色:后厨的完整流程

后厨完整流程是:接单、翻译菜谱、备料、烹饪、出餐。编译也是如此:前端生成高层 IR,经过规范化、降级、优化,最后代码生成。标准方言在流水线中担任枢纽。

第一个动作是规范化(canonicalization):把冗余表达化简,比如 x + 0 直接替换为 x,x × 1 直接删除。第二个动作是多级降级:所有上层方言统一翻译成标准方言后,后续优化只需面对一种 IR——编译器团队写的 pass 一次编写,适用于所有前端。

标准方言的“统一表示”带来两个直接收益。一是可扩展性:新方言不需要写完整后端,只要会降级到标准方言即可。二是代码生成质量:LLVM 后端看到的是一份经过规范化的、语义清晰的 IR,能应用更多优化,减少资源浪费。

一张干净的标准方言 IR,就像一份工整的备料单:名厨看一眼知道菜怎么炒,LLVM 看一眼知道代码怎么生成。规范,是质量的第一道保障。

arith.addi(x,0)  ⇒  x\text{arith.addi}(x, 0) \;\Rightarrow\; x
规范化示例:加零这个冗余操作在流水线早期被化简,为后续优化减轻负担。
  1. 左侧操作arith.addi(x, 0) 表示对 x 加上常数 0。
    arith.addi(x,0)\text{arith.addi}(x, 0)
  2. 规范化规则0 是加法单位元,整个操作退化为 x,冗余计算被删除。
    ⇒\Rightarrow
  3. 化简结果仅保留 x,减少一次无意义运算。
    xx

AI 应用场景

推理引擎的算子降级

阶段:模型编译与部署阶段

解决的问题:PyTorch/TensorFlow 导出的高层算子(如卷积、LayerNorm)无法被硬件直接执行;传统做法是手写每类算子的底层实现,工作量大且不可复用。Torch-MLIR 等工具先把 torch 方言降级为 arith、tensor 等标准方言操作,再落到 LLVM,一条路径服务所有模型。

具体案例:某公司将 ResNet 的 ONNX 模型接入自研推理引擎:前端把 onnx.Conv 翻译成 tensor.extract、arith.mulf、arith.addf 等标准操作序列,经规范化和降级后生成 LLVM IR,最终在 CPU 上跑通 200 帧/秒。

自研 AI 芯片的编译器接入

阶段:硬件后端开发阶段

解决的问题:新芯片没有现成编译器,若从头写“算子到硬件指令”的翻译器成本极高。借助标准方言,团队只需编写少量从自定义方言到标准方言的重写模式,社区已有的标准方言→LLVM 后端可直接复用。

具体案例:NPU 团队为自家 tpu.dispatch 算子编写重写规则,分解为 arith.addi、tensor.extract 等标准操作,再经标准流水线生成可执行代码,工具链开发周期从一年缩短到三个月。

调试数值不一致的算子

阶段:性能调优与 Bug 排查阶段

解决的问题:训练与推理结果不一致时,高层面板语义太厚难以定位;把 IR 打印到标准方言粒度后,可逐条核对算术操作和类型转换。

具体案例:工程师发现复现性报错源于一处有符号/无符号除法混用,查看降级后的 cmpi 与 divsi 指令后定位到取整方向错误,修复后对齐了 cuDNN 的数值结果。

Python 演示

python41 行
"""mini_mlir_lower.py: 模拟 MLIR 渐进式降级的小工具. 把高层方言(Toy)的复合操作逐条翻译成标准方言(Arith/Tensor)指令,并打印降级后的指令流, 帮助理解标准方言在编译流水线中的角色.""" # 高层方言到标准方言的"菜谱拆解表"LOWER_TABLE = {    "toy.reshape": ["tensor.collapse_shape", "std.memref_cast"],    "toy.multiply": ["arith.mulf"],    "toy.relu": ["arith.maxf", "std.select"],    "toy.conv": ["tensor.extract", "arith.mulf", "arith.addf"],}  def lower_one(op_name):    """把一个高层操作拆解为标准方言操作序列."""    if op_name in LOWER_TABLE:        return LOWER_TABLE[op_name]    return [op_name]  # 已是标准方言则原样保留  def lower_pipeline(ops):    """对整个流水线做渐进降级, 返回标准方言指令列表."""    lowered = []    for op in ops:        lowered.extend(lower_one(op))    return lowered  def main():    pipeline = ["toy.conv", "toy.relu", "toy.reshape"]    result = lower_pipeline(pipeline)    print("=== 标准方言指令流 ===")    for i, inst in enumerate(result, 1):        print(f"{i:2d}. {inst}")    print(f"共 {len(result)} 条指令, 已消除全部高层自定义算子")  if __name__ == "__main__":    main()
目的:用一张“菜谱拆解表”模拟 MLIR 方言转换,演示高层面板操作如何渐进降级为标准方言指令序列。
  1. LOWER_TABLE = {定义“菜谱拆解表”:每种高层面板操作对应一串标准方言指令。
  2. def lower_one(op_name):单个操作降级入口,从拆解表查规则。
  3. if op_name in LOWER_TABLE:命中已注册的重写模式,完成模式匹配。
  4. lowered.extend(lower_one(op))用 extend 拼接多步指令,保持执行顺序。
  5. for i, inst in enumerate(result, 1):编号打印降级后的指令流。
  6. if __name__ == "__main__":保证模块可被导入而不自动执行主流程。

常见误区

误区我刚学 MLIR 时以为标准方言就是最上面一层的 IR,新功能直接写成标准方言操作最省事,反正它叫“标准”。

正解恰恰相反,标准方言位于抽象阶梯的底部通用层,语义非常朴素(标量算术、结构操作)。把卷积这种复合语义直接拆成标准方言,会变成一大串指令,丢失可优化结构。正确做法是先在自定义高层面板表达丰富语义,最后再渐进降级到标准方言,就像先用创意菜谱沟通、再翻译成基础动作。

为何常见:“标准/内置”听起来像正统顶层 API,容易和“用户编程语言库”混淆。

误区我以为类型就是整数和浮点两个标签,写代码够用了,反正示例都是 `addi(i32, i32)`。

正解标准方言提供 index、i1、i8-i64、f16/f32/f64、complex、tensor、memref、vector 等一整套基础类型。index 专门描述索引,tensor 描述不可变数据,memref 描述可变内存。类型系统是完整的食材标签体系,远不止两种。

为何常见:入门教程示例多用 addi 的 i32 签名,容易以偏概全。

误区`tensor.extract`、`tensor.reshape` 这些名字听起来就是线性代数库函数,矩阵乘应该也在里面。

正解标准方言的张量操作只负责结构——构建、取元素、改形状,不含矩阵乘这类密集数学运算。矩阵乘在 linalg/math 方言,或经 bufferization 后由循环加标量算术表达。把结构操作当成数学计算,会误解整个 IR 分层设计。

为何常见:把 PyTorch 的 tensor API 印象直接套到了编译器 IR 上。

误区一次重写从高层面板直接翻译到机器码最省时间,为什么还要渐进多步?

正解一步降级会同时丢失大量中间语义,优化 pass 无处下手、Bug 难以定位。渐进降级像后厨先拆菜谱再做菜:方言→标准方言→LLVM,每步只降一级抽象,每一步都可验证,社区复用度也高。

为何常见:编译过程不可见,容易高估单步重写的可行性。

误区标准方言操作最后不都变成 LLVM 指令吗?干脆当成同一层。

正解标准方言仍有较高语义:有类型丰富的 tensor/memref、有运行时分配语义;LLVM IR 是更底层的 SSA + 基本块,类型是指针、向量、标量的机器精确表示。标准方言必须经过逐条映射和内存降级(memref→指针)才到达 LLVM。

为何常见:两者都叫“中间表示”,且降级界线看起来模糊。

面试题

Q1. MLIR 标准方言在编译框架中的核心定位是?

单选题 · easy

  • A.面向用户的编程高级语言
  • B.高低层之间的公共基础层← 正确答案
  • C.直接执行运算的硬件引擎
  • D.AI 专用算子数学库

答案:B。标准方言不是给用户写程序用的(那是前端语言的事),也不是硬件执行引擎。它像后厨的基础动作清单,位于高层面板与 LLVM 之间,是承上启下的公共基础层。A/C/D 都把层级搞错了。

Q2. 以下哪个是标准方言中的基础类型?

单选题 · easy

  • A.索引位置使用的 index 类型← 正确答案
  • B.网页超文本标记类型
  • C.操作系统文件句柄类型
  • D.数据库事务日志类型

答案:A。index 是标准方言用于表达索引与地址的基础类型,降级时会转换为指针宽度的具体整数。B/C/D 分别是网页、操作系统、数据库领域的概念,均不属于 MLIR 的类型体系。

Q3. arith.addi 与 arith.addf 的核心区别是?

单选题 · mid

  • A.前者是整数加法后者浮点加法← 正确答案
  • B.前者是浮点加法后者整数加法
  • C.两者语义完全相同没区别
  • D.前者是张量加法后者是标量加法

答案:A。命名后缀就是刀型标签:i 代表 integer,f 代表 float,所以 addi 做整数加法、addf 做浮点加法,两者类型签名不同。C 忽略后缀差异,D 混淆了“标量/张量”与“整数/浮点”两组正交维度。

Q4. cmpi 指令比较后的输出类型通常为?

单选题 · mid

  • A.i1 布尔标量类型← 正确答案
  • B.任意宽度的整数类型
  • C.与输入相同的类型
  • D.浮点张量结果类型

答案:A。cmpi 是一条“味觉判断”指令,只回答真/假,所以输出固定为单比特 i1。B 把输入宽度误当作输出,C 忽略了比较与算术的差异,D 混淆了数值运算与比较运算的分工。

Q5. tensor 类型与 memref 类型的核心区别是?

单选题 · hard

  • A.张量不可变而 memref 引用可变内存← 正确答案
  • B.张量描述内存布局而 memref 保存数值
  • C.两者完全相同没有实质差别
  • D.memref 类型不包含形状信息

答案:A。tensor 像摆好的拼盘(不可变),memref 像可随时加菜的大锅(可变内存引用)。二者在降级中通过 bufferization 衔接:先用 tensor 表达计算,再落到 memref 分配内存。C 与事实矛盾,B 张冠李戴,D 错误——memref 同样携带形状信息。

Q6. 渐进式降级(progressive lowering)指的是?

单选题 · mid

  • A.逐层降低抽象直到低层表示← 正确答案
  • B.一次性直接翻译到机器码
  • C.从 LLVM 反向推导高层面板
  • D.跳过中间表示直接生成目标代码

答案:A。渐进降级像把菜谱先拆成基础步骤再执行:每层只降一级抽象(方言→标准方言→LLVM),保留中间优化机会、便于调试。B/D 一步到位会丢失中间语义,C 方向完全反了。

Q7. select 指令在 SSA 形式中的作用是?

单选题 · hard

  • A.按条件从两个值中挑选一个← 正确答案
  • B.修改条件变量的内存内容
  • C.完整执行两个分支的全部逻辑
  • D.无条件地把两个数组连接起来

答案:A。SSA 中值不可变,没有传统 if 变量修改的能力,select 像大厨尝味后“选加盐还是加水”,按布尔条件输出两值之一。B 违反 SSA 不可变原则,C 属于控制流合并,D 是拼接操作,均非 select 语义。

Q8. 下列选项中属于标准方言操作的是?

多选题 · mid

  • A.arith.addi 整数加法← 正确答案
  • B.cmpi 整数比较指令← 正确答案
  • C.torch.nn 卷积模块
  • D.tensor.extract 张量取值← 正确答案

答案:ABD。arith.addi、cmpi、tensor.extract 分别是算术、比较、张量结构操作,属于标准方言。torch.nn 是 PyTorch 的高层语义,属于深度学习框架层面,不在标准方言范围内。

Q9. 关于标准方言降级到 LLVM,下列说法正确的是?

多选题 · extreme

  • A.index 类型需转换为具体整数← 正确答案
  • B.标准方言逐条映射为 LLVM 指令← 正确答案
  • C.张量直接映射为机器寄存器
  • D.memref 最终转为指针访问形式← 正确答案

答案:ABD。index 在目标机上有具体宽度,必须显式转换;标准方言逐条映射为 LLVM 指令;memref 在 bufferization 后变成指针加形状描述,最终以指针访存。张量不能直接映射寄存器,必须先完成布局与缓冲分配,C 错误。

Q10. 标准方言提升编译器可扩展性的方式包括?

多选题 · easy

  • A.提供统一稳定的基础语义← 正确答案
  • B.新方言可直接复用降级路径← 正确答案
  • C.禁止新方言重写基础操作
  • D.强制用户从头编写编译后端

答案:AB。标准方言提供共同的基础语义和成熟的 LLVM 降级路径,新方言像新菜谱只需翻译成基础动作,不必自己造炉子。C 与事实不符——新方言完全可以把复杂算子重写为标准操作序列;D 违背可扩展性设计初衷。