凌晨两点的快递分拨中心,传送带轰隆作响。一票从青岛寄往成都的活蟹包裹,正经历一场“语言接力”:月台扫描员念着“库位 A3-12”,干线车调度员喊出“K102 次、冷链舱”,最后配送员在面单上写下“滨江路 17 号 301”。同一票货,不同环节用不同的“黑话”描述,但所有人都能协作——因为包裹上贴着一张全公司统一的面单。AI 编译器面对的世界也是如此:PyTorch 训练出的模型、GPU 的矩阵指令、NPU 的张量搬运,各自说着完全不同的“语言”。MLIR 就是那张统一面单,方言(Dialect)则是每个环节的岗位黑话。本文要讲的“自定义方言设计”,恰似为活蟹包裹新开一条冷链专线:先算账要不要开(需求分析),再定工牌和库位编码(方言骨架),规定每个动作怎么做(操作定义),贴上冷链规格签(类型系统),到转运点拆包重装(降级转换),最后让专线与整车场无缝衔接(集成验证)。这篇教程会带你走完这条专线的全流程。

岗位关联

掌握自定义方言设计,意味着你不仅能阅读和调度 MLIR 的既有表达,还能为 AI 模型和硬件芯片之间架起一座专属桥梁——这是编译器工程师与推理引擎开发者最核心的能力之一。

目录
  1. 可视化演示
  2. 需求分析定位
  3. 方言骨架搭建
  4. 操作定义规范
  5. 类型系统设计
  6. 降级转换实现
  7. 架构匹配适配
  8. 集成验证测试
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示MLIR自定义方言设计:多层IR逐级lowering、ODS操作定义到C++代码生成,以及Pattern Rewriting重写规则,支持步进切换与参数调节。

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

多层IR降级 高层tosa/mhlo逐级lowering high→mid→low→llvm IR逐层逼近硬件后端

需求分析定位

快递公司不会因为一个客户说“我想寄螃蟹”就立刻买一百辆冷链车。运营会先翻数据:现有常温线路能覆盖多少、客户愿意为保鲜多付多少钱、周边城市有没有可借用的冷链资源。同样的,在 MLIR 里“造一个方言”最忌拍脑袋。动手写代码前,得先回答三个问题:领域里有哪些独一无二的概念?现有通用 IR 表达这些概念时哪里难受?目标硬件希望我们用什么样的方式描述计算?

这三个问题分别对应方言的三大价值。第一,领域概念的直接表达:把“卷积”写成若干 reshape 和 dot 的组合,不如直接写一个 conv2d 让人一眼看懂,编译器也更容易抓住融合与复用机会。第二,通用 IR 的局限:builtin 方言只有最基础的类型和操作,无法表达“这个张量的物理布局是 NHWC”或“这个算子必须在 DMA 引擎上执行”。第三,硬件特征的抽象:芯片的寄存器、内存层次、并行单元,都需要方言提供“离家门口最近”的一层描述。

实践中,人们常用一份简短的评估清单来启动方言项目:列出候选操作集、估计每个操作为优化器带来的收益、对比用通用方言拼凑要付出的重写成本。收益越高、成本越低,就越值得立项。这几项权衡可以量化为一个价值公式:

就像冷链专线的开通论证报告,公式里的每一项都对应着真实投入:表达力是客户愿意付的保价费,优化潜力是少损耗一箱蟹的利润,硬件匹配是让车队不空跑的前提。三项加权得分高,才值得立项动工。

VD=wE×E+wP×P+wH×C\displaystyle V_D = w_E \times E + w_P \times P + w_H \times C
这个公式告诉你:方言的价值不是“写了多少行代码”,而是三个可感知收益——表达力、优化潜力、硬件匹配度——按权重相加的结果。
  1. V_D(方言价值)代表这个自定义方言在开发周期内带来的总收益,数值越高越值得投入。
    VDV_D
  2. w_E × E(表达力收益)E 是领域概念被直接表达的收益,w_E 是它在决策里的权重。
    wE×Ew_E \times E
  3. w_P × P(优化潜力收益)P 是高层次优化能释放的性能空间,如算子融合与消除冗余。
    wP×Pw_P \times P
  4. w_H × C(硬件匹配收益)C 是方言对目标硬件特性的贴合程度,w_H 是硬件约束的权重。
    wH×Cw_H \times C

方言骨架搭建

冷链专线立项后,第一步不是买蟹,而是定规矩:库区叫“冷一区”、工牌戴蓝帽、面单前缀加“LL“。这些看似琐碎的规矩,让每个员工在交接时不用猜对方在说什么。MLIR 里搭建方言骨架也是同理:明确命名空间、声明依赖、组织文件、注册进上下文——一套”工牌与编码体系”。

MLIR 的方言是一个 C++ 对象,通过继承 mlir::Dialect 创建,并注册到 MLIRContext 这个“总登记台”。命名规范要求方言名小写开头、不含特殊字符,操作名统一为“方言名。操作名”。依赖声明告诉编译器:本方言用到了 builtin 或其他自定义方言的哪些类型、属性和接口,从而构筑一张清晰的依赖网,避免循环引用。

通常一个方言项目包含 TableGen 声明文件(ODS)、C++ 实现文件和 CMake 构建脚本。ODS 文件用声明式语言描述操作、类型、属性,自动生成 C++ 类与解析打印代码——这就像先把各岗位的 SOP 写成手册,再统一翻译成培训课程。方言的完整名称也有固定格式:

工牌、编码、手册都齐了,冷链专线才算有了“身份”。方言骨架搭好,后续的操作和类型定义才有安放之处,团队协作效率也会大幅提升。

OpName=Namespace+.+BaseName\displaystyle OpName = Namespace + \text{.} + BaseName
操作名由“方言命名空间 + 点号 + 操作基本名”三部分组成。只要看到全名,就知道这个操作属于哪个方言、在做什么。
  1. Namespace(方言命名空间)所有操作共用的前缀,如 toy 表明来自 toy 方言。
    NamespaceNamespace
  2. 连接点号分隔命名空间与操作基本名的固定字符,防止歧义。
    .\text{.}
  3. BaseName(操作基本名)表示具体动作,如 add、silu、conv2d。
    BaseNameBaseName

操作定义规范

快递公司每开一条新业务线,都要给一线员工发一份详细的操作手册:接到活蟹单先核验保价、装冰袋要放底层、面单必须贴在外箱右上角。MLIR 的自定义方言同样需要把“业务动作”定义得明明白白——操作(Operation)就是方言世界里的业务动作。

每个操作有操作数(输入物料)、结果(产出)、属性(参数)和区域(子过程)。操作数对应包裹物料,结果是加工后的产出,属性是“冰袋数量”“温控模式”这类静态参数。MLIR 的 ODS 框架允许用声明式写法定义这些元素,并为操作标注特性,如“无副作用”“可交换”等,让优化器敢大胆做变换。

操作名必须唯一,格式是“方言名。操作名”;操作数数量、每个操作数的类型、结果的数量都应在定义中声明;操作验证逻辑保证不合法的调用在构造阶段就被拦截,而不是等到代码生成后崩溃。操作合法性可以表达为一条逻辑约束:

操作手册写得细,交接就不会出错。同理,操作定义得严谨,后续的降级和优化才能在稳固的地基上盖楼。

Verify(op)=(NumOperands(op)=N)∧TypeMatch(op)\displaystyle Verify(op) = (NumOperands(op) = N) \wedge TypeMatch(op)
操作的合法性校验包含两个必须同时满足的条件:操作数数量等于声明值,且所有操作数与结果的类型互相匹配。
  1. Verify(op)编译器对操作执行的合法性校验,决定该操作能否进入后续优化。
    Verify(op)Verify(op)
  2. NumOperands(op) = N操作数的实际个数必须等于定义时声明的数量 N。
    NumOperands(op)=NNumOperands(op) = N
  3. TypeMatch(op)操作数与结果的类型必须满足声明中的约束关系。
    TypeMatch(op)TypeMatch(op)
  4. ∧(逻辑与)两个条件必须同时成立,只要一个不满足,校验就失败。
    ∧\wedge

类型系统设计

冷链业务里,“活蟹”和“冰鲜鱿鱼”不能混放。光靠操作手册还不够,得有一套标签体系:保温箱上贴着不同的温度等级贴纸,分拣机才能自动分流。在 MLIR 中,这套贴纸就是类型系统。

自定义类型用于约束操作数的语义。操作数不能只看成“张量”或“整数”,还要表达“这是一张 NHWC 布局的量化张量”或“这是一个分布在多卡上的分块张量”这类深层信息。类型可以在 ODS 中用 TypeDef 声明,并携带参数,如形状、元素类型、布局方式。

类型参数要定义编码、解析和打印三件事:怎样存进内存、怎样从文本读出来、怎样展示给人看。类型约束校验则保证只有语义匹配的类型才能连到同一个操作上,等于分拣机只认贴对的标签。类型占用的存储空间可以这样计算:

标签贴对,分拣效率才高;类型定义得好,整个流水线就能在编译期把“不可混放”的错误拦截下来,而不是等包裹送到客户手上才被发现。

S(T)=∑p∈Params(T)size(p)\displaystyle S(T) = \sum_{p \in Params(T)} size(p)
自定义类型 T 的总存储大小,等于其携带的所有参数 p 的存储大小之和。每个参数都是类型身份的一部分。
  1. S(T)自定义类型 T 在内存中占用的总字节数,直接影响 IR 的紧凑程度。
    S(T)S(T)
  2. Params(T)该类型声明携带的参数集合,如张量的形状、布局、量化信息。
    Params(T)Params(T)
  3. Σ(求和)对集合中每个参数的存储空间做累加,得到类型的完整体积。
    ∑\sum
  4. size(p)单个参数编码后的存储大小,由该参数的类型与精度决定。
    size(p)size(p)

降级转换实现

冷链车到了城市转运中心,能不能直接开进每个小区?不能。你需要在转运点把活蟹从泡沫箱里取出,套上保温袋、放进社区冷链柜,重新打印一张末端配送面单。这个过程,就是编译器里的“降级”(Lowering)。

降级把高层方言操作替换成低层方言操作组合,是自定义方言与现有工具链对接的必经之路。MLIR 提供了一套声明式机制:ConversionTarget 声明“哪些方言最终要留下来”,RewritePattern 定义“每个操作如何被拆解替换”,Pass 驱动整体转换的工序。

开发者在 ConversionTarget 中标记不合法(要被替换)的操作,在 RewritePattern 中实现“匹配 + 重写”两个动作,最后通过 Pass 运行“部分转换”或“完全转换”流水线。匹配失败时操作会被保留,并通过诊断信息提示开发者补全规则。降级可以抽象为一级一级的翻译:

拆包重装的动作越标准,末端配送越顺畅;降级规则写得越完整,高层模型就越能顺利落到各类硬件指令上。

IRi+1=Loweri(IRi)\displaystyle IR_{i+1} = Lower_i(IR_i)
第 i+1 层 IR,是把第 i 层 IR 交给第 i 个降级 Pass 处理后的结果。整个编译流水线就是这样一级一级“翻译”下去的。
  1. IR_{i+1}转换后得到的新一级中间表示,抽象层次更低、更贴近硬件。
    IRi+1IR_{i+1}
  2. Lower_i流水线中第 i 个降级 Pass 的转换行为,内部包含多个 RewritePattern。
    LoweriLower_i
  3. (IR_i)括号表示该转换函数作用在当前的 IR 上,输入输出都是 IR。
    (IRi)(IR_i)

架构匹配适配

同样是冷链运输,长途干线用重型冷冻车,同城急送用保温三轮车,小区投递用冷链柜。不同的车有不同装载和行驶特性,专线的设计必须因车而异——这就是硬件匹配。

自定义方言的最后归宿通常是特定硬件:GPU、NPU 或 DSP。方言分层(如高层算子方言 → 循环/向量方言 → LLVM 方言)可以看作车队里不同车型的调度体系。为了让硬件跑得最快,方言设计需要显式表达并行度、内存布局和访存模式。

硬件感知的优化贯穿其中:算子融合把多次访存合并成一次,向量化利用计算单元的宽度,内存布局控制把数据排布成硬件最顺手的形状。方言能否表达这些信息,直接决定优化器能做到多好。最终的执行耗时可以建模为:

车型和货物匹配,整条运输链才最高效;方言和硬件特征匹配,模型才能在芯片上榨出每一分算力和带宽。

T(D)=Tcomp(D)+Tmem(D)\displaystyle T(D) = T_{comp}(D) + T_{mem}(D)
在目标硬件上执行某段方言描述的计算,总耗时由计算时间与访存时间相加得到。方言设计要同时优化这两个部分。
  1. T(D)目标硬件上执行该方言描述计算的总耗时,是性能评估的核心指标。
    T(D)T(D)
  2. T_comp(D)计算核心消耗的时间,取决于算子指令数量与并行单元利用率。
    Tcomp(D)T_{comp}(D)
  3. T_mem(D)访存与搬运数据消耗的时间,取决于内存布局与缓存命中率。
    Tmem(D)T_{mem}(D)
  4. 加法(+)总耗时由计算和访存两部分共同构成,只优化一边往往收效有限。
    ++

集成验证测试

冷链专线全线铺通,不能直接宣布“开业”。你得先跑几十票测试货,跟踪每一票的温控记录和送达时间,再和普通线路对比,最后才敢接大客户的单。方言落地的后半程,就是集成、验证、迭代。

新方言要注册进 MLIRContext,作为转换 Pass 挂入编译流水线,和已有的 tensor、linalg、llvm 等方言协同工作。MLIR 提供 mlir-opt 工具,开发者可以编写文本 IR 测试用例,验证方言的解析、打印、验证、降级行为是否正确。

功能测试用 FileCheck 断言 IR 转换结果;集成测试把自定义方言放进完整的“前端图 → 高层方言 → 低层方言 → LLVM IR”流水线;性能测试则和基准路径对比,算出加速比:

只有跑过压力测试的冷链线,才敢承诺“死蟹包赔”;只有经过完整验证的方言,才值得交付给生产环境。这条专线从此正式并入物流大网,成为整个分拨中心能力的一部分。

S=TbaselineTcustom\displaystyle S = \frac{T_{baseline}}{T_{custom}}
加速比是接入自定义方言后的性能提升倍数。比值大于 1,说明自定义流水线比通用路径更快。
  1. S(加速比)衡量自定义方言相对基准路径的性能提升,是上线决策的核心数字。
    SS
  2. T_baseline(基准耗时)使用通用编译与执行路径时的耗时,作为对比的起点。
    TbaselineT_{baseline}
  3. 分式线(相除)用基准耗时除以新路径耗时,得到相对快了多少倍。
    TbaselineTcustom\frac{T_{baseline}}{T_{custom}}
  4. T_custom(自定义耗时)接入自定义方言流水线后的端到端耗时。
    TcustomT_{custom}

AI 应用场景

PyTorch 自定义算子的硬件加速

阶段:图优化与算子编译

解决的问题:传统方式中,PyTorch 模型里的自定义激活函数(如 SiLU)会被拆成一批基础 torch 算子逐个执行,频繁启停内核,GPU 利用率低;用自定义方言把整个激活过程描述为一个高层操作,再由降级 Pass 直接翻译成融合的底层指令,可减少内核启动次数与中间张量读写。

具体案例:某推荐排序模型在部署到 GPU 前,把 hard-swish 激活写成自定义方言操作,经过 linalg-on-tensors 的向量化与融合,最终生成单次 kernel 调用,端到端推理延迟降低 23%。

AI 芯片的后端方言适配

阶段:后端代码生成与硬件适配

解决的问题:新 NPU 的指令集没有成熟编译器后端,芯片团队需要一层描述硬件指令的“设备方言”,否则模型只能靠手写算子库,开发周期按月计算。

具体案例:某自动驾驶芯片团队围绕自研 NPU 定义 ex_npu 方言,封装矩阵乘加、张量搬移、激活融合三类指令;Torch-MLIR 的前端 IR 通过多级降级最终落到 ex_npu 方言,再转成汇编,新算子接入从两周缩短到两天。

框架编译栈的统一 IR 接入

阶段:前端捕获与统一优化

解决的问题:PyTorch 2.0 编译栈需要把 eager 模式的动态图转换为可静态优化的 IR。若每个框架各搞一套中间表示,就无法共享优化逻辑。

具体案例:Torch-MLIR 项目为 PyTorch 定义 torch 方言,先捕获前向计算图,再降级到 TOSA 与 linalg 方言,让 MLIR 社区已有的融合、向量化、调度 Pass 直接复用,最终生成 LLVM IR 或接入各类硬件后端。

Python 演示

python63 行
class Operation:    """模拟 MLIR 中的操作节点"""     def __init__(self, dialect, op_name, operands, attrs=None):        self.dialect = dialect        self.op_name = op_name        self.operands = list(operands)        self.attrs = attrs or {}     def full_name(self):        # 方言前缀 + 点号 + 操作名,例如 toy.silu        return "{0}.{1}".format(self.dialect, self.op_name)     def __repr__(self):        operands_repr = ", ".join(o.full_name() for o in self.operands)        return "{0}({1})".format(self.full_name(), operands_repr)  class Dialect:    """模拟 MLIR 方言命名空间"""     def __init__(self, name):        self.name = name     def __repr__(self):        return self.name     def make(self, op_name, operands, attrs=None):        return Operation(self, op_name, operands, attrs)  def lower_silu(op, dialect):    """把 toy.silu 降级为 sigmoid(x) * x 两个操作"""    x = op.operands[0]    sigmoid = dialect.make("sigmoid", [x])    mul = dialect.make("mul", [x, sigmoid])    return [sigmoid, mul]  def lower_toy_to_std(toy_ops, std_dialect):    """自定义方言 -> 基础方言的降级流水线"""    lowered_ops = []    for op in toy_ops:        if op.op_name == "silu":            lowered_ops.extend(lower_silu(op, std_dialect))        else:            lowered_ops.append(op)    return lowered_ops  def main():    toy = Dialect("toy")    std = Dialect("std")    x = std.make("input", [], {"name": "x"})    silu = toy.make("silu", [x])    graph = [x, silu]    print("降级前:", graph)    result = lower_toy_to_std(graph, std)    print("降级后:", result)  if __name__ == "__main__":    main()
目的:用 Python 面向对象的方式,模拟 MLIR 中“自定义方言定义 + 降级转换”的核心流程——把 toy.silu 这样一个高层自定义操作,降级为 sigmoid 与 mul 两个基础操作。
  1. class Operation:表述 MLIR 中的操作节点,记录归属方言、操作名、操作数和属性。
  2. return "{0}.{1}".format(self.dialect, self.op_name)拼接“方言名。操作名”的完整唯一名称。
  3. def lower_silu(op, dialect):定义一个降级规则,对应 MLIR 里的 RewritePattern。
  4. sigmoid = dialect.make("sigmoid", [x])创建第一个基础操作,把原操作数传进去。
  5. mul = dialect.make("mul", [x, sigmoid])创建第二个基础操作,组合成 x * sigmoid(x)。
  6. if op.op_name == "silu":模式匹配阶段,判断当前操作是否命中降级规则。
  7. lowered_ops.extend(lower_silu(op, std_dialect))把单个高层操作展开为多个基础操作,加入结果列表。
  8. if __name__ == "__main__":提供可执行入口,运行后能直接看到降级前后 IR 的变化。

常见误区

误区“我只要在 ODS 里多写几个 def,比如 my.add、my.mul,方言就算设计好了。名字起得越炫,越显得专业。”

正解方言的核心是“语义契约”。名字只是命名空间前缀;真正关键的是操作数与结果的约束、类型系统、验证逻辑和降级规则。没有这些约束,编译器无法安全地对操作做任何优化。就像光给包裹贴“海鲜”两个字,不写保存温度和配送时限,分拣员根本不知道该怎么处理。

为何常见:操作名是最直观的表面,初学者容易把“写名字”当成全部工作;加上 ODS 写起来确实简单,进一步误导了认知。

误区“既然都在同一个 MLIRContext 里,toy 方言的操作应该能自动被 linalg 方言处理。大家都是同一套 IR,互相之间应该无障碍吧?”

正解方言之间不会自动转换。你必须为它们编写显式的降级 Pass 和 RewritePattern,告诉编译器 toy.reshape 怎么拆成 linalg 操作。否则编译器只会在诊断里报无法匹配 pattern 的错误。正如快递公司不会因为两辆车停在同一个停车场,就认为货物能自动从一辆车跳上另一辆车。

为何常见:MLIR 的口号是“统一基础设施”,听起来很像“统一语言”,让人误以为所有方言天然互相理解。

误区“与其维护那么多小方言,不如把张量、循环、向量、硬件指令全部塞进一个 myall 方言,一步到位。”

正解方言的粒度决定优化的粒度。混合不同抽象层次的方言会让 Pass 难以定位目标:既难匹配算子模式,也会让验证规则纠缠在一起。MLIR 的设计哲学是“多级、分层”,小而聚焦的方言才能像积木一样灵活组装。冷链、常温、同城、干线各司其职,才是高效网络。

为何常见:从单体编译器(比如直接基于 LLVM IR)迁移过来的人习惯“一套 IR 管到底”,容易抵触分层设计。

误区“tensor<2xf32> 不是就能表达所有张量吗?自定义类型是多余的。”

正解通用张量类型表达不了“NHWC 布局”“量化缩放因子”“多卡分布式分块”这类语义。如果这些信息在 IR 中缺失,优化器只能做保守假设,生成大量内存搬运,性能大打折扣。自定义类型的本质是让编译器“看得到”那些原本只能靠命名约定传递的信息。

为何常见:学习阶段接触的都是 builtin 类型,没遇到过布局或量化问题,因此低估了类型语义的重要性。

面试题

Q1. 在 MLIR 中,方言(Dialect)存在的根本意义是什么?

单选题 · easy

  • A.让编译器代码文件更美观
  • B.提供可扩展的中间表示机制← 正确答案
  • C.替代目标硬件的驱动程序
  • D.提升前端语言的执行速度

答案:B。方言是 MLIR 实现“统一基础设施 + 领域可扩展”的关键机制,它让陌生领域的概念以统一 IR 的形式表达,而不是用一套固定 IR 硬撑所有场景。A、C、D 都偏离了方言在编译栈中的定位。

Q2. 开始设计自定义方言时,第一步应该做的是?

单选题 · easy

  • A.立即编写操作定义的代码
  • B.收集领域专有概念与痛点← 正确答案
  • C.确定编译器的输出目录
  • D.申请云端 GPU 测试资源

答案:B。需求分析是方言设计的起点。先弄清楚领域里有哪些独特概念、通用 IR 在哪里表达吃力,才能确定方言要覆盖的边界。跳步直接写代码,就像没算账就买冷链车。

Q3. 一个操作在 MLIR 中的完整名称格式是什么?

单选题 · easy

  • A.方言名加点连接操作名← 正确答案
  • B.仅使用操作名的英文缩写
  • C.编译器随机分配的数字编号
  • D.操作数加类型标注即可

答案:A。MLIR 要求操作名带命名空间前缀,格式为“方言名。操作名”,例如 toy.silu。这样不同方言里的同名操作也不会冲突,编译器能准确定位操作归属。其他选项都违背了命名规范。

Q4. 一次方言降级(Lowering)过程中会发生什么?

单选题 · mid

  • A.将高层操作替换为低层操作← 正确答案
  • B.将低层操作提升为高层操作
  • C.删除图中所有中间表示节点
  • D.仅调整操作的打印排版格式

答案:A。降级的本质是“翻译”:把抽象层次高的操作拆解、替换成更贴近硬件的低层操作序列。B 方向反了,C 删除所有中间表示会破坏 IR,D 把降级误解为格式化。

Q5. 使用 ODS 定义操作时,通常要声明的内容包括?

多选题 · mid

  • A.操作数、结果与属性列表← 正确答案
  • B.操作对应的 CPU 序列号
  • C.自定义的验证与打印规则← 正确答案
  • D.开发团队的分工与考勤表

答案:AC。ODS 的核心声明包括操作数、结果、属性,以及可选的验证逻辑和自定义解析/打印格式。B、D 属于运行时或项目管理信息,与编译期 IR 定义无关。

Q6. 在 MLIR 中,自定义类型的主要作用是什么?

单选题 · mid

  • A.为操作提供更强的语义约束← 正确答案
  • B.让编译器界面背景更美观
  • C.避免操作数占用任何内存
  • D.替代所有内置的基础类型

答案:A。自定义类型把布局、量化、分布等语义信息带进 IR,让约束校验和优化决策有据可依。B、C、D 都是对类型系统的错误理解。

Q7. 编写一个降级 Pass 通常需要提供哪些内容?

多选题 · mid

  • A.转换目标集合与重写模式← 正确答案
  • B.待转换的高层操作匹配规则← 正确答案
  • C.适配原有方言的打印换行符
  • D.目标硬件的官方价格文档

答案:AB。一个完整的降级 Pass 需要 ConversionTarget 声明哪些操作不合法、需要替换,以及 RewritePattern 定义匹配与重写逻辑。C、D 与降级机制无关。

Q8. 当降级模式与高层操作不匹配时,结果通常是什么?

单选题 · hard

  • A.保留该操作并给出诊断提示← 正确答案
  • B.编译器自动修改硬件设计
  • C.所有操作被静默地直接丢弃
  • D.编译流水线无限循环运行

答案:A。MLIR 降级框架的默认行为是“转换不了就保留原操作”,并输出诊断信息提醒开发者补全规则。静默丢弃会破坏 IR,无限循环和改硬件更是天方夜谭。

Q9. 自定义方言上线前需要验证的内容包括?

多选题 · hard

  • A.功能正确性测试文件用例← 正确答案
  • B.端到端的性能基准对比← 正确答案
  • C.与其他方言转换链路衔接← 正确答案
  • D.开发人员代码风格偏好

答案:ABC。方言交付前要验证功能正确(FileCheck 断言)、转换链路完整(高层到低层全流程)、性能达标(加速比 S)。代码风格属于工程规范,不是上线验证项。

Q10. 关于 MLIR 中 ODS / TableGen 的描述,正确的是哪一项?

单选题 · extreme

  • A.按声明生成操作类与打印代码← 正确答案
  • B.仅生成编译器的命令行帮助文档
  • C.是运行时解释执行的方言解析器
  • D.硬编码各个目标平台的机器码

答案:A。ODS 基于 TableGen 在编译期自动生成操作类、类型类、验证函数和解析/打印代码,属于“声明式生成”。B 把能力说窄了,C 说成运行时解释,D 说成平台硬编码,均不符合 ODS 的工作原理。