惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Scott Helme
Scott Helme
N
Netflix TechBlog - Medium
AI
AI
Security Latest
Security Latest
GbyAI
GbyAI
P
Proofpoint News Feed
Y
Y Combinator Blog
A
Arctic Wolf
G
Google Developers Blog
U
Unit 42
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
L
Lohrmann on Cybersecurity
C
CERT Recently Published Vulnerability Notes
C
Check Point Blog
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 【当耐特】
博客园 - Franky
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
Latest news
Latest news
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
WordPress大学
WordPress大学
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
The Hacker News
The Hacker News
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
Project Zero
Project Zero
博客园_首页

Mox的笔记库

MacOS配置《明日方舟:终末地》 | Mox的笔记库 2025:向内生长 | Mox的笔记库 由mlir::ExecutionEngine引发的跨系统问题 | Mox的笔记库 WSL2配置Cuda-Tile环境记录(未完待续) | Mox的笔记库 Vibe Coding手搓项目记录 | Mox的笔记库 给Debian上包——以DuckDB为例 | Mox的笔记库 UCPD.sys事件存档 | Mox的笔记库 换新电脑之Mac mini M4从购买到配置 | Mox的笔记库 Mac配置MLX-C开发环境 | Mox的笔记库 RISC-V meets RDBMS——RISC-V架构上可运行数据库一览 | Mox的笔记库 DuckDB Sort实现调查 | Mox的笔记库 修复Redis在树莓派5上无法运行的问题 | Mox的笔记库 如何在MLIR中自定义类型并且输出运行 | Mox的笔记库 网站网络结构变更记录 | Mox的笔记库 EDBT25论文阅读:PhoebeDB——A Disk-Based RDBMS Kernel for High-Performance and Cost-Effective OLTP SIGMOD25论文阅读:BPF-DB:——A Kernel-Embedded Transactional Database Management System For eBPF Applications Apache Arrow Gandiva项目解析 | Mox的笔记库 VLDB24论文阅读:Cloud-Native Database Systems and Unikernels——Reimagining OS Abstractions for Modern Hardware NoisePage源码分析(未完待续) | Mox的笔记库 VLDB20论文阅读:Mainlining Databases——Supporting Fast Transactional Workloads on Universal Columnar Data File Formats VLDB17论文阅读:Relaxed Operator Fusion for In-Memory Databases:Making Compilation, Vectorization, and Prefetching Work Together At Last 论文阅读:How not to structure your database-backed web applications——a study of performance bugs in the wild SIGMOD24阅读:ROME——Robust Query Optimization via Parallel Multi-Plan Execution 文章阅读:First Past the Post-Evaluating Query Optimization in MongoDB SIGMOD文章阅读:Apache Calcite——A Foundational Framework for Optimized Query Processing Over Heterogeneous Data Sources VLDB23论文阅读:Analyzing the Impact of Cardinality Estimation on Execution Plans in Microsoft SQL Server SIGMOD22论文阅读:Efficient Massively Parallel Join Optimization for Large Queries VLDB论文阅读:Weaving Relations for Cache Performance VLDB22论文阅读:ConnectorX——Accelerating Data Loading From Databases to Dataframes 论文阅读:UniKraft-Fast, Specialized Unikernels the Easy Way 当DuckDB遇上RISC-V | Mox的笔记库 SIGMOD25论文阅读:An Elephant Under The Microscope——Analyzing The Interaction Of Optimizer Components In PostgreSQL 论文阅读:Compile-Time Analysis of Compiler Frameworks for Query Compilation VLDB23阅读:Bringing Compiling Databases to RISC Architectures SIGMOD24文章阅读:Query Compilation Without Regrets | Mox的笔记库 淦!MLIR输出Hello World不应该这么难! | Mox的笔记库 2024:拥挤年代的想象与创造 | Mox的笔记库 如何给自己的博客添加MLIR和LLVM IR语法高亮 | Mox的笔记库 博客重构:从Hexo到Astro | Mox的笔记库 VLDB19-Parsing Gigabytes of JSON per Second论文阅读 CIDR25:Runtime-Extensible Parsers阅读 | Mox的笔记库 SIGMOD24文章阅读:VeriTxn | Mox的笔记库 MLIR学习资料整理 | Mox的笔记库 VLDB23文章阅读——Exploiting Cloud Object Storage for High-Performance Analytics VLDB24——OLAP on Modern Chiplet-Based Processors走马观花阅读 如何让数据库中的Python跑的更快-VLDB22-YeSQL文章阅读 | Mox的笔记库 你好,世界! | Mox的笔记库 如何愉快的运行一个MLIR程序 | Mox的笔记库 让系统研究更有意义:HarmonyOS NEXT的教训和经验——讲座回顾 | Mox的笔记库 VLDB22:YeSQL文章阅读(已废弃) | Mox的笔记库 UNSW 24T3 COMP9336上课记录 | Mox的笔记库 Velox开发环境配置踩坑记录 | Mox的笔记库 LingoDB源码编译与分析 | Mox的笔记库 论文阅读:Declarative Sub-Operators for Universal Data Processing 论文阅读:Designing an Open Framework for Query Optimization and Compilation MLIR Toy Tutorial实践记录 | Mox的笔记库 2024年7月RSSHub开发体验 | Mox的笔记库 LLVM-Kaleidoscope实操踩坑记录 | Mox的笔记库 澳洲大学计算机硕士比较 | Mox的笔记库 论文阅读——CDUL:CLIP-Driven Unsupervised Learning for Multi-Label Image Classification CVPR2023-CLIP算法调研 | Mox的笔记库 论批量快速添加图片与视频水印的事 | Mox的笔记库 基于元信息写入的服务器压力测试 | Mox的笔记库 家庭组网IPv6+Mesh折腾 | Mox的笔记库 MjAyMw==,希望,前进与平庸之道 | Mox的笔记库 code-server初体验 | Mox的笔记库 从Nginx到Caddy | Mox的笔记库 RMM观察与初探 | Mox的笔记库 计算机网络课设——UDP/TCP/TLS Socket实验 | Mox的笔记库 JQuery的XSS初探 | Mox的笔记库 生产实习记录 | Mox的笔记库 Fedora-CoreOS配置与试用(2023年) | Mox的笔记库 Electron学习笔记 | Mox的笔记库 ServerSentEvent学习 | Mox的笔记库 报告翻译:容器云的安全挑战 | Mox的笔记库 Vagrant配置Metarget靶场环境 | Mox的笔记库 OpenAI-whisper折腾 | Mox的笔记库 202202,困惑,混乱与未曾设想之路 | Mox的笔记库 2022年Hack the box:Tier1免费区全解 | Mox的笔记库 Navidrome部署记录 | Mox的笔记库 长安杯2021-snake复现 | Mox的笔记库 报告概要翻译:OBFUSCATING C++ PROGRAMS VIA CONTROL FLOW FLATTENING 从零开始的Django CVE-2022-28346复现 | Mox的笔记库 2022CISCN(西北区赛)-The shinning | Mox的笔记库 Docker+QEMU+Arm64(Ubuntu)+环境配置(2022版) | Mox的笔记库 Arch Linux运行树莓派系统(2022年) | Mox的笔记库 2022CISCN初赛-ez_usb-复盘WriteUp | Mox的笔记库 Arch Linux迁移计划 | Mox的笔记库 Django事务使用 | Mox的笔记库 记录第一次EduSRC上报 | Mox的笔记库 Jetbrain问题应急处理 | Mox的笔记库 Celery5.2学习&配置 | Mox的笔记库 Waline部署记录 | Mox的笔记库 Frida hook初次实战 | Mox的笔记库 NodeMCU-MicroPython配置实录 | Mox的笔记库 Log4j2漏洞复现 | Mox的笔记库 2022长安“战疫”网络安全卫士守护赛回顾 | Mox的笔记库 2021年12月 Vivo千镜杯回顾 | Mox的笔记库 Windows的WSL2+Docker初探 | Mox的笔记库 Hexo部署安装全流程回顾 | Mox的笔记库
2026PPoPP MLIR Tutorial学习 | Mox的笔记库
MocusEZ · 2026-02-27 · via Mox的笔记库

这个月初参加了CGO的LLVM Workshop,在悉尼ICC的2楼做完报告后就到3楼溜达(3楼在开CC,以及PPoPP/HPCA的Workshop和Tutroial),顺带就看了一下MLIR Tutorial。我觉得这个Tutrorial做的不错,讲的是用MLIR实现个简单的Tile,但当天身体不太舒服提前走了,直到这几天才基本完整过一遍Tutroial

IMG_20260131_120150

项目地址:Groverkss/mlir-tutor

按照项目的Readme.md配置即可顺利运行

tutorial-opt注意事项

项目的Opt位于build/tutorial/tutorial-opt

opt输出可以选择--split-input-file将同一个文件的不同Op进行输出分割

如果需要运行运行Python程序,则需要注意TUTORIAL_OPT环境变量是否则正确

通过grep可以查看对应Pass(总感觉这块显示并不怎么好,看看有没有方法改进)

tutorial-opt --help | grep "tiny"

MLIR与Python联动

这是这个Tutorial吸引人的地方,其提供了一套非常基础的Python与MLIR的Type与Op绑定

比如下面这个是Ptr绑定

class Ptr:

"""Wrapper for !tiny.ptr SSA values."""

_value: Value

@staticmethod

def _wrap(value: Value) -> "Ptr":

p = Ptr()

p._value = value

return p

@staticmethod

def get_type() -> Type:

"""Get the !tiny.ptr type."""

return Type.parse("!tiny.ptr")

def load(self, offset: Index, num_elements: int) -> F16Vector:

"""Load vector<Nxf16> from pointer at offset."""

vec_type = VectorType.get([num_elements], F16Type.get())

op = Operation.create(

"tiny.load",

results=[vec_type],

operands=[self._value, offset._value],

)

return F16Vector._wrap(op.result)

def store(self, offset: Index, vec: F16Vector) -> None:

"""Store vector<Nxf16> to pointer at offset."""

Operation.create(

"tiny.store",

results=[],

operands=[vec._value, self._value, offset._value],

)

Compile_and_print将Python转化为MLIR并输出

def compile_and_print(fn):

"""Compile and print all lowering stages."""

opt = TutorialOpt()

with MLIRModule() as m:

tiny_ir = m.build_func_verified(fn, _get_type_map(), opt)

print("=== Tiny Dialect ===")

print(tiny_ir)

arith_ir = opt.run(tiny_ir, ["tiny-to-arith", "canonicalize", "cse"])

print("=== After tiny-to-arith ===")

print(arith_ir)

llvm_ir = opt.run(tiny_ir, ["tiny-to-arith", "canonicalize", "cse", "tiny-to-llvm", "convert-to-llvm"])

print("=== LLVM Dialect ===")

print(llvm_ir)

return llvm_ir

同样,MLIR的Vector Type也与Numpy的Vector进行绑定(Numpy的array转为mlir的vector),才代码来看方法也一并绑定

class F16Vector:

"""Wrapper for vector<Nxf16> SSA values."""

_value: Value

@staticmethod

def _wrap(value: Value) -> "F16Vector":

vec = F16Vector()

vec._value = value

return vec

@staticmethod

def constant(vals: list[float], size: int = None) -> "F16Vector":

"""Create a constant f16 vector via tiny.constant."""

n = size or len(vals)

vec_type = VectorType.get([n], F16Type.get())

data = np.array(vals, dtype=np.float16)

attr = DenseElementsAttr.get(data, type=vec_type)

op = Operation.create(

"tiny.constant",

results=[vec_type],

attributes={"value": attr},

)

return F16Vector._wrap(op.result)

def _binop(self, other: "F16Vector", op_name: str) -> "F16Vector":

op = Operation.create(

f"tiny.{op_name}",

results=[self._value.type],

operands=[self._value, other._value],

)

return F16Vector._wrap(op.result)

def __add__(self, other): return self._binop(other, "addf")

def __sub__(self, other): return self._binop(other, "subf")

def __mul__(self, other): return self._binop(other, "mulf")

def __truediv__(self, other): return self._binop(other, "divf")

def sum(self) -> "F16Vector":

"""Reduce to vector<1xf16> via tiny.sum."""

result_type = VectorType.get([1], F16Type.get())

op = Operation.create(

"tiny.sum",

results=[result_type],

operands=[self._value],

)

return F16Vector._wrap(op.result)

此外还需要对于MLIRModule进行单独Python包装,以适应不同的场景

class MLIRModule:

"""Context manager for building MLIR modules with unregistered dialects."""

def __init__(self):

self.ctx = None

self.loc = None

self.module = None

def __enter__(self):

self.ctx = Context()

self.ctx.allow_unregistered_dialects = True

self.ctx.__enter__()

self.loc = Location.unknown()

self.loc.__enter__()

return self

def __exit__(self, *args):

self.loc.__exit__(*args)

self.ctx.__exit__(*args)

def build_func(self, fn: Callable, type_map: dict) -> Module:

"""Build MLIR module from a Python function.

Args:

fn: Function to compile (uses type annotations for args)

type_map: Maps annotation types to (mlir_type, wrapper_class) tuples

"""

sig = inspect.signature(fn)

self.module = Module.create()

with InsertionPoint(self.module.body):

# Build input types from annotations

input_types = []

for param in sig.parameters.values():

if param.annotation not in type_map:

raise ValueError(f"Unsupported type: {param.annotation}")

mlir_type, _ = type_map[param.annotation]

input_types.append(mlir_type() if callable(mlir_type) else mlir_type)

# Create func.func

func_op = func_d.FuncOp(fn.__name__, (input_types, []))

with InsertionPoint(func_op.add_entry_block()):

# Wrap block arguments in DSL types

args = []

for i, param in enumerate(sig.parameters.values()):

_, wrapper_cls = type_map[param.annotation]

args.append(wrapper_cls._wrap(func_op.arguments[i]))

# Execute user function body

fn(*args)

# Add return

func_d.return_([])

return self.module

def build_func_verified(self, fn: Callable, type_map: dict, opt: "TutorialOpt") -> str:

"""Build and verify module, return pretty-printed IR.

Runs the generated IR through tutorial-opt to verify it's valid

and get pretty-printed output using the dialect's assembly format.

"""

self.build_func(fn, type_map)

raw_ir = str(self.module)

# Round-trip through tutorial-opt to verify and pretty-print

return opt.run(raw_ir, [])

此外还有一些实现细节需要注意

概念说明
tutorial-optC++编译的MLIR工具,实现了所有pass(tiny-to-arith、tiny-to-llvm等)
TutorialOptPython包装器,通过 subprocess 调用 tutorial-opt
run() 方法构建命令行参数,执行 tutorial-opt 进程,返回结果
pass 列表指定要执行的MLIR转换通道
stdin/stdout通过标准输入传入IR,从标准输出读取转换结果

Chapter1

Pass需要,且也可以在单独的TableGen中定义

//===- TinyPasses.td - Tiny dialect passes -----------------*- tablegen -*-===//

//

// Defines passes for the Tiny dialect.

//

// Reference: https://mlir.llvm.org/docs/PassManagement/#tablegen-specification

//

//===----------------------------------------------------------------------===//

#ifndef TINY_PASSES

#define TINY_PASSES

include "mlir/Pass/PassBase.td"

def TinyToArith : Pass<"tiny-to-arith"> {

let summary = "Lower Tiny arithmetic operations to arith dialect.";

let description = [{

This pass lowers Tiny dialect arithmetic operations to equivalent operations

in the arith dialect. Memory operations (load/store) and the ptr type are

NOT converted by this pass - use --tiny-to-llvm for that.

The lowering includes:

- `tiny.constant` -> `arith.constant`

- `tiny.addf/subf/mulf/divf` -> `arith.addf/subf/mulf/divf`

- `tiny.addi/subi/muli/divi` -> `arith.addi/subi/muli/divsi`

Example:

```mlir

%0 = tiny.addf %a, %b : vector<4xf16>

// Becomes:

%0 = arith.addf %a, %b : vector<4xf16>

```

}];

let dependentDialects = [

"mlir::arith::ArithDialect",

"mlir::vector::VectorDialect"

];

}

def TinyToLLVM : Pass<"tiny-to-llvm"> {

let summary = "Lower Tiny memory operations and ptr type to LLVM dialect.";

let description = [{

This pass lowers Tiny dialect memory operations and the ptr type to

equivalent operations in the LLVM dialect.

The lowering includes:

- `!tiny.ptr` type -> `!llvm.ptr`

- `tiny.load %ptr, %offset` -> GEP to compute byte address, then `llvm.load`

- `tiny.store %val, %ptr, %offset` -> GEP to compute byte address, then `llvm.store`

The offset in tiny.load/store is in f16 elements. The lowering converts this

to a byte offset by using GEP with f16 element type:

```mlir

%0 = tiny.load %ptr, %offset : vector<4xf16>

// Becomes:

%gep = llvm.getelementptr %ptr[%offset] : (!llvm.ptr, i64) -> !llvm.ptr, f16

%0 = llvm.load %gep : !llvm.ptr -> vector<4xf16>

```

Note: Run --tiny-to-arith first to convert arithmetic operations.

}];

let dependentDialects = [

"mlir::LLVM::LLVMDialect"

];

}

#endif // TINY_PASSES

非常规范的Type定义与Operate定义,CPred从的isPowerOf2_64中起着校验参数的作用

// Constraint for vector<Nxf16> where N is a power of 2.

// Uses VectorOfRankAndType from CommonTypeConstraints.td (included via OpBase.td)

// with an additional power-of-2 size check.

def Tiny_VectorF16 : Type<

And<[

VectorOfRankAndType<[1], [F16]>.predicate,

CPred<"::llvm::isPowerOf2_64("

"::llvm::cast<::mlir::VectorType>($_self).getDimSize(0))">

]>,

"vector of f16 with power-of-2 size",

"::mlir::VectorType"

>;

类型的定义还可以继承

// Constraint for vector<1xf16> (result type for sum operation).

// Reuses Tiny_VectorF16 predicate and adds a size=1 constraint.

def Tiny_Vector1F16 : Type<

And<[

Tiny_VectorF16.predicate,

CPred<"::llvm::cast<::mlir::VectorType>($_self).getDimSize(0) == 1">

]>,

"vector<1xf16>",

"::mlir::VectorType"

>;

ConstantOp确定了既可以是vector也可以是index

def Tiny_ConstantOp : Tiny_Op<"constant", [Pure,

AllTypesMatch<["value", "result"]>]> {

let summary = "Creates a constant vector or index value.";

let description = [{

The `tiny.constant` operation creates a constant value which can be either

a vector<Nxf16> or an index.

Examples:

```mlir

%0 = tiny.constant dense<[1.0, 2.0, 3.0, 4.0]> : vector<4xf16>

%1 = tiny.constant 42 : index

```

}];

// TypedAttrInterface allows the type to be inferred from the attribute.

let arguments = (ins TypedAttrInterface:$value);

let results = (outs AnyTypeOf<[Tiny_VectorF16, Index]>:$result);

// The attribute itself contains the type, so no need to print it separately.

let assemblyFormat = "attr-dict $value";

}

Op的Interface里有SameOperandAndResultType选项

class Tiny_IndexBinaryOp<string mnemonic, list<Trait> traits = []> :

Tiny_Op<mnemonic, !listconcat([Pure, SameOperandsAndResultType], traits)> {

let arguments = (ins Index:$lhs, Index:$rhs);

let results = (outs Index:$result);

let assemblyFormat = "$lhs `,` $rhs attr-dict";

}

tiny dialect的运算操作转向arith dialect和vector dialect

struct SumOpLowering : public OpRewritePattern<SumOp> {

using OpRewritePattern<SumOp>::OpRewritePattern;

LogicalResult matchAndRewrite(SumOp op,

PatternRewriter &rewriter) const override {

Location loc = op.getLoc();

Value input = op.getInput();

// vector.reduction<add> returns a scalar f16.

Value scalarSum = vector::ReductionOp::create(

rewriter, loc, vector::CombiningKind::ADD, input);

// Broadcast the scalar to vector<1xf16>.

VectorType resultType = op.getResult().getType();

rewriter.replaceOpWithNewOp<vector::BroadcastOp>(op, resultType, scalarSum);

return success();

}

};

tiny dialect的指针操作与运算转向LLVM Dialect

class TinyToLLVMTypeConverter : public TypeConverter {

public:

TinyToLLVMTypeConverter() {

// Identity conversion for all types (fallback).

addConversion([](Type type) { return type; });

// Convert tiny.ptr to llvm.ptr.

addConversion([](PtrType type) -> Type {

return LLVM::LLVMPointerType::get(type.getContext());

});

}

};

struct StoreOpToLLVMLowering : public OpConversionPattern<StoreOp> {

using OpConversionPattern<StoreOp>::OpConversionPattern;

LogicalResult

matchAndRewrite(StoreOp op, OpAdaptor adaptor,

ConversionPatternRewriter &rewriter) const override {

Location loc = op.getLoc();

// The adaptor provides the converted operands (ptr is now !llvm.ptr).

Value value = adaptor.getValue();

Value ptr = adaptor.getPtr();

Value offset = adaptor.getOffset();

// Convert index offset to i64 for GEP.

Type i64Type = rewriter.getI64Type();

Value offsetI64 =

arith::IndexCastOp::create(rewriter, loc, i64Type, offset);

// Create GEP with f16 element type to compute the address.

Type f16Type = rewriter.getF16Type();

Type llvmPtrType = LLVM::LLVMPointerType::get(getContext());

Value gep = LLVM::GEPOp::create(rewriter, loc, llvmPtrType, f16Type, ptr,

ValueRange{offsetI64});

// Store the vector to the computed address.

rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, value, gep);

return success();

}

};

将func dialect设为dynamic legal

class TinyToLLVMPass : public impl::TinyToLLVMBase<TinyToLLVMPass> {

public:

void runOnOperation() override {

// Set up the type converter.

TinyToLLVMTypeConverter typeConverter;

// Set up the conversion target.

ConversionTarget target(getContext());

// Mark memory operations as illegal.

target.addIllegalOp<LoadOp, StoreOp>();

// Mark LLVM and arith dialects as legal.

target.addLegalDialect<LLVM::LLVMDialect>();

target.addLegalDialect<arith::ArithDialect>();

// Mark func dialect operations as dynamically legal if their types are

// converted.

target.addDynamicallyLegalOp<func::FuncOp>([&](func::FuncOp op) {

return typeConverter.isSignatureLegal(op.getFunctionType()) &&

typeConverter.isLegal(&op.getBody());

});

target.addDynamicallyLegalOp<func::ReturnOp>([&](func::ReturnOp op) {

return typeConverter.isLegal(op.getOperandTypes());

});

// Set up rewrite patterns.

RewritePatternSet patterns(&getContext());

// Add conversion patterns that use the type converter.

patterns.add<LoadOpToLLVMLowering, StoreOpToLLVMLowering>(typeConverter,

&getContext());

// Add function signature conversion patterns.

populateFunctionOpInterfaceTypeConversionPattern<func::FuncOp>(

patterns, typeConverter);

populateReturnOpTypeConversionPattern(patterns, typeConverter);

// Apply the conversion.

if (failed(applyPartialConversion(getOperation(), target,

std::move(patterns))))

signalPassFailure();

}

};

Chapter2

这个章节主要是将Tiny转为SCF,对于Python则需要将For循环转为SCF

以Accumulate举例,这会Python的decorate方法转化为tiny_loop.accumulatetiny_loop.yield

def decorator(body_fn):

# Get init value types and MLIR values

init_values = [v._value for v in inits]

init_types = [v._value.type for v in inits]

result_types = init_types # Results match init types

# Create the accumulate op with one region

op = Operation.create(

"tiny_loop.accumulate",

results=result_types,

operands=[bound._value, step._value] + init_values,

regions=1, # One region for the body

)

# Set up the block with arguments: (index, *iter_args)

region = op.regions[0]

block_arg_types = [IndexType.get()] + init_types

block = Block.create_at_start(region, block_arg_types)

# Execute body with wrapped arguments

with InsertionPoint(block):

iv = Index._wrap(block.arguments[0])

iter_args = [_wrap_value(block.arguments[i+1], inits[i])

for i in range(len(inits))]

# Call user's body function

if inits:

results = body_fn(iv, *iter_args)

if not isinstance(results, (list, tuple)):

results = [results]

yield_values = [r._value for r in results]

else:

body_fn(iv)

yield_values = []

# Create tiny_loop.yield

Operation.create("tiny_loop.yield", operands=yield_values)

# Wrap and return results

if result_types:

return [_wrap_value(op.results[i], inits[i])

for i in range(len(result_types))]

return None

return decorator

matemul.py则演示了矩阵乘法,如果之前有人看过OpenAI Triton的话应该不会对这种形式感到陌生

这是一个 向量化矩阵乘法的实现,演示如何使用 Ch2 的循环构造(accumulate)来编写高性能的矩阵乘法。

算法:C[M,N] = A[M,K] * B[K,N]^T

关键点:

  • B被转置,使得A和B都在K维上是连续的(便于向量化加载)
  • 向量大小为16(一次加载16个f16元素)
  • 三层嵌套循环:M维、N维、K维

ch2有一个单独的tiny_loopDialect,实现了SCF的Accumulate(对应scf.for)和Yield(对应scf.yield)

Chapter3

Chapter3实现了一个类似Trition和TileIR的Tile实现

对应的则是tiny_tile这个Dialect,下降时会用的GPU Dialect(只是输出,并不运行)

对于定义Tile这个Type,使用assemblyformat无法实现对应解析要求,需要实现相对应对的parseprint

Type TileType::parse(AsmParser &parser) {

if (parser.parseLess())

return Type();

// Parse "HxW" as a dimension list. MLIR's lexer treats "64x128" as a single

// dimension list token, so we must use parseDimensionList.

SmallVector<int64_t, 2> dims;

if (parser.parseDimensionList(dims, /*allowDynamic=*/false,

/*withTrailingX=*/false))

return Type();

// We expect exactly 2 dimensions for a 2D tile.

if (dims.size() != 2) {

parser.emitError(parser.getCurrentLocation())

<< "expected 2 dimensions for tile, got " << dims.size();

return Type();

}

// Parse required comma and layout attribute.

if (parser.parseComma())

return Type();

LayoutAttr layout;

if (parser.parseAttribute(layout))

return Type();

if (parser.parseGreater())

return Type();

return TileType::get(parser.getContext(), dims[0], dims[1], layout);

}

/// Print a tile type: `<` HxW `,` layout `>`

void TileType::print(AsmPrinter &printer) const {

printer << "<" << getHeight() << "x" << getWidth() << ", " << getLayout() << ">";

}

对于tiny_tile.splat在规划好矩阵的同时,也一并规划好线程

#tiny_tile.layout<thread = [1, 32], vector_size = 8>

Layout的Parameter定义,可以看到可以参数使用的是int64_t(在MLIR中这是一个Attribute)

let parameters = (ins

ArrayRefParameter<"int64_t">:$thread,

"int64_t":$vectorSize

);

Tile的尺寸由thread和vector_size共同计算得到

例子1:thread = [4, 16], vector_size = 4

线程网格:4行 × 16列 = 64个线程 Tile尺寸:4 × (16*4) = 4×64 = 256个元素

(2D的Thread可以利用好GPU Warp的特性)

tiny_tile可以lowering到tiny和tiny_loop,线程部分lowering到GPU Dialect

The lowerings should produce:

  • LoadOp -> gpu.thread_id + tiny.load (compute per-thread offset from layout)

  • StoreOp -> gpu.thread_id + tiny.store (compute per-thread offset from layout)

  • SumOp -> tiny.sum + vector.extract + gpu.subgroup_reduce + vector.broadcast

而对于线程用二维进行表示的原因如下

概念说明
thread[0]Y方向(垂直)的线程数 = thread_y的范围
thread[1]X方向(水平)的线程数 = thread_x的范围
vector_size每个线程处理的向量宽度
Tile实际尺寸thread[0] × (thread[1] × vector_size)
映射方向行优先(Row-major):先填满X(列),再增加Y(行)
好处利用GPU硬件特性,提高缓存局部性和执行效率

Chapter3的gpu_dot_product.py在用tiny_tile做乘法

这是一个 GPU并行点积(dot product)计算 的实现,使用 Tile-based DSL 在多个GPU块上进行SPMD(Single Program Multiple Data)计算

头一次见到Op的定义可以带有空格(实际上是当作EnumAttrbute传入)

- tiny_tile.elementwise add -> tiny.addf

- tiny_tile.elementwise sub -> tiny.subf

- tiny_tile.elementwise mul -> tiny.mulf

- tiny_tile.elementwise div -> tiny.divf

def TinyTile_EWKind_Add : I32EnumAttrCase<"add", 0>;

def TinyTile_EWKind_Sub : I32EnumAttrCase<"sub", 1>;

def TinyTile_EWKind_Mul : I32EnumAttrCase<"mul", 2>;

def TinyTile_EWKind_Div : I32EnumAttrCase<"div", 3>;

def TinyTile_ElementwiseKind : I32EnumAttr<

"ElementwiseKind",

"Elementwise operation kind",

[TinyTile_EWKind_Add, TinyTile_EWKind_Sub,

TinyTile_EWKind_Mul, TinyTile_EWKind_Div]> {

let cppNamespace = "::mlir::tiny_tile";

let genSpecializedAttr = 0;

}

def TinyTile_ElementwiseKindAttr :

EnumAttr<TinyTile_Dialect, TinyTile_ElementwiseKind, "ew_kind">;

通过getkind()决定往哪个方向下降

LogicalResult ElementwiseOp::convertToSIMT(RewriterBase &rewriter,

ValueRange simtOperands) {

Value lhs = simtOperands[0];

Value rhs = simtOperands[1];

switch (getKind()) {

case ElementwiseKind::add:

rewriter.replaceOpWithNewOp<tiny::AddFOp>(*this, lhs, rhs);

break;

case ElementwiseKind::sub:

rewriter.replaceOpWithNewOp<tiny::SubFOp>(*this, lhs, rhs);

break;

case ElementwiseKind::mul:

rewriter.replaceOpWithNewOp<tiny::MulFOp>(*this, lhs, rhs);

break;

case ElementwiseKind::div:

rewriter.replaceOpWithNewOp<tiny::DivFOp>(*this, lhs, rhs);

break;

}

return success();

}

tutorial/ch3-gpu-tile-dsl/TinyTileDialect.cpp实现了很多类型的convertToSIMT方法,文件中的注释值得细看

操作convertToSIMT 做什么输入(Tile)输出(Vector)
SplatOp创建 per-thread 向量常数标量值tiny.constant dense<…> : vector
LoadOp计算 per-thread 地址偏移ptr, row, col, stride%thread_id + 地址计算 → tiny.load
StoreOp计算 per-thread 地址偏移value, ptr, row, col, stride%thread_id + 地址计算 → tiny.store
SumOp两级归约(本地+跨线程)vectortiny.sum + extract + subgroup_reduce + broadcast

Challenge Exercise是做一个tiny_tile的matmul

由于对GPU Dialect不太熟悉,这一章的转化感觉没看懂

Chapter 4

主要关于Linage Dialect和Transform Dialect,从而实现Tile的算子融合

文档中提到Transform Dialect参考的是Halide IR(“Halide like”),这个说法我是第一次听到(如果你再仔细问AI的话,会发现Halide是TVM和MLIR祖先)

Halide非常有意思,因为其对算法和调度进行了区分

范畴属于“算法”吗?属于什么?
加减乘除、数学函数(sin, exp)计算逻辑
像素间的依赖关系(如均值模糊)计算逻辑
循环的顺序(先行后列,还是分块)调度 (Schedule)
是否使用多线程 (Parallel)调度 (Schedule)
是否使用向量化 (Vectorize)调度 (Schedule)
临时缓冲区的大小和位置调度 (Schedule)

“算法与调度分离”有三个核心原因

  1. 数学上的正确性:算法部分是容易验证的。只要数学公式对了,无论调度怎么变(分块、并行),计算结果理论上应该是一致的。
  2. 模块化:同一个算法可以有多种调度方案。例如,针对手机 CPU 有一套调度,针对高性能 GPU 有另一套调度,但算法代码一行都不用改
  3. 算子融合(Fusion):因为算法是纯函数描述,编译器可以清晰地看到函数间的依赖关系,从而自动决定是否把两个步骤合并在一起计算,而不需要程序员手动去拆解循环。

这部分基本是MLIR官方Tutorial的简化版本,如果实在不明白就看MLIR官网的Transform Dialect


avatar

探索未曾设想的道路