ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用MLIR编译LLM应用:从胶水脚本到可分析程序

用MLIR编译LLM应用:从胶水脚本到可分析程序 一个看起来正常的 LLM 应用第一版往往只需要二三十行 Python拼 prompt调模型接口解析返回 JSON再根据结果决定要不要继续调用下一个模型。真正让人难受的不是第一版跑不通而是它在一个月后开始失控——prompt 改了输入结构跟着变A 模型返回{name: Tom}B 模型返回Tom某个环节在 JSON 前后夹了一段解释文字解析器直接崩掉。你会发现自己维护的不是代码而是一堆靠记忆维系的隐式契约。Linkly 这个项目标题恰好回应了这个问题它想为 LLM 应用设计一门语言并且通过 MLIR 来编译而不是再做一个解释执行的编排框架。以 Show HN 形式出现意味着它可能还很早期但“language MLIR”这个组合本身就值得认真讨论。它真正想解决的不是“少写一点 Python”而是把 LLM 应用从“不稳定的胶水脚本”变成“可编译、可分析、可复核的软件产物”。下面我按自己的理解把这个方向拆开聊一聊。1. 为什么 LLM 应用需要一门“语言”而不是又一个框架1.1 今天的 LLM 流程本质是一堆弱类型胶水你写一个典型的 RAG 或 Agent 流程通常会是这样的形状用户 query 进来先判断意图再做检索检索结果拼成 context再调一次大模型生成回答。每个阶段之间传的都是什么Python 字典、JSON 字符串或者某个对象里一层又一层嵌套的字段。比如 A 阶段输出{documents: [{text: ..., score: 0.8}]}B 阶段需要的是text但到了 C 阶段你可能又要取documents[0].score。这些依赖在代码里确实写出来了可没有人能在运行前告诉你如果score不存在如果返回字段换成了中文键名如果模型没有按 prompt 要求返回 JSON会发生什么。这就是弱类型胶水代码。语言层面的原生类型可能很美好但一到 LLM 流程里几乎所有东西都被降级成了字符串和字典。更麻烦的是LLM 的输出本身不稳定于是我们还要在代码里写一堆.get(key, )、if isinstance(...)、json.loads套try except。这些逻辑本质上是“运行时契约检查”只是做得特别隐式、特别零散。1.2 编排框架解决的是“连接”不是“契约”最近半年很多人都在讨论 LLM 应用为什么需要编排框架。市面上已经有大量工具把流程画成 DAG用节点表示 prompt、召回、工具调用用边表示依赖关系。这确实比纯 Python 脚本清晰因为它把“谁先执行”显式化了。但“编排”解决的是流程拓扑不解决“上一个节点给你的到底是什么”。你用框架连好两个节点上游输出仍然可以是一个 JSON 字符串下游节点仍然要在自己的逻辑里猜测字段格式。框架可以做重试、可以限制并发、可以做缓存但它通常不会在你运行之前告诉你上下游数据契约不匹配。很多人觉得链式调用很脆弱不是因为 DAG 不够多而是因为 DAG 里流动的数据缺少一种统一、可描述、可检查的格式。此时真正需要的不是又一个“流程配置器”而是一套能够表达输入输出契约、能够在编译期做校验的抽象层。1.3 从“解释执行”到“编译期检查”Linkly 的选择是把 LLM 流程当作一门语言来设计。它不一定真的需要像 C 语言那样编译成机器码但它可以把“阶段”“输入输出类型”“prompt 模板”“工具调用”“重试策略”这些元素变成语言的原生概念。这个思路和直接写 Python 最大的区别是你不再只是“执行脚本”而是在“构建程序”。一旦流程变成程序就有了语法、类型、作用域、数据流、控制流。编译器可以在运行前做很多检查这个 stage 的输入类型是不是和上一个 stage 的输出类型匹配某个分支条件是不是永远不可能发生输出 schema 里声明的字段是否真的在后续 stage 被使用这些检查听起来很朴素但放到 LLM 应用里它们恰恰是现在最缺失的部分。很多人把大量时间花在调试“模型为什么不按我写的格式返回”却没有想过格式契约本身应该被一种更稳定的机制管理起来而不是只靠 prompt 里的自然语言和 Python 里的 try except。我把这个变化理解成LLM 应用开发正在从“写脚本”走向“写程序”。Linkly 不是第一个提出这个方向的项目但它选择了 MLIR 作为编译基础设施这个选择很特别。2. 编译到 MLIR它不是又一个解释器而是在编译器基础设施上重构2.1 MLIR 是给编译器用的“多级中间表示”MLIR 是 LLVM 社区的一部分全称是 Multi-Level Intermediate Representation中文常译作“多级中间表示”。它不是一个具体的中间语言而是一套用来构造编译器的框架。理解 MLIR 最核心的概念是“方言”dialect和“降级”lowering。传统编译器通常只有一层很接近机器的中间表示比如 LLVM IR。MLIR 则允许你定义多个抽象层级的方言。你可以先定义和业务高度贴近的高层方言再通过一系列 pass 把它逐步降级到更接近底层实现的方言最后落到 LLVM IR、CPU、GPU 或自定义运行时上。这个设计对 Linkly 真正的意义在于Linkly 不需要一开始就把 LLM 流程硬翻译成机器指令而是可以保留流程语义。先有阶段、数据契约、分支、重试这些高层概念再逐步把它们编译成具体运行时上的调用。你可以类比成建筑设计。不会有人从一张概念草图直接跳到钢筋图纸中间需要经过结构模型、机电模型、施工图等多轮转换。MLIR 提供的正是这种“多级转换”能力而不是一次性把设计图变成钢筋。2.2 如果 Linkly 真把 LLM 流程定义成方言合理图景是什么从项目标题看Linkly 很可能定义了自己的高层语言。它可能有类似 pipeline、stage、tool、prompt、output_type 这样的语法。编译时这些语法会被映射到一个自定义的 MLIR dialect 上。在这个 dialect 里每个 LLM 操作都可能变成一个 op比如llm.call表示一次模型调用llm.tool表示一个工具调用节点llm.output_check表示对模型输出做 schema 校验llm.retry表示失败重试策略这些 op 保留高层语义所以编译器可以在这一层做输入输出类型匹配、数据流分析和死代码消除。比如发现某个 stage 的返回值从来没人用就可以在 IR 层给出告警发现某个工具调用的入参类型和上游输出对不上可以在编译期报错而不是等运行时崩溃。之后这些高层 op 再通过降级变成具体后端的调用。如果跑的是云端模型 API就降级成 HTTP 请求如果跑的是本地推理引擎就降级成本地 runtime 调用。只要方言和降级路径设计得足够好同一份流程程序理论上可以换后端执行而不需要重写业务逻辑。我必须强调这是基于“通过 MLIR 编译”这个标题做的合理推演不是 Linkly 官方文档里已经写完的能力。对于一个早期项目来说真正实现到什么程度还需要看代码和设计文档。但这个方向最迷人的地方在于它不是给 Python 套一层语法糖而是从根上改变 LLM 流程的表示方式。2.3 编译到 MLIR 的长期价值可分析、可优化、可复用的流程制品解释执行一个脚本时你手里只有源代码和运行结果中间过程往往不透明。编译到 MLIR 之后流程的中间表示变成一个结构化的数据产物。这意味着三件事可分析可以在 IR 上写检查 pass比如检测 prompt 模板变量是否在输入 schema 中都存在检测某个模型调用是否缺少输出约束。可优化可以合并重复的模型调用可以把一部分确定性逻辑提前计算可以把检索排序等操作和主流程解耦。可复现源码、编译产物、IR、运行时日志可以形成一一对应关系。出了问题时你不再只有一个模糊的报错堆栈而是能定位到某个 stage、某个 IR op、某个运行时调用。这听起来很理想但也要看到成本设计一门语言、定义方言、写编译器 pass、承接动态运行时这些工作量远超写一个 Prompt 编排框架。Linkly 真正要证明的不是“能不能编译”而是这套编译器的复杂度值不值得换回稳定性。3. 如果让我上手试 Linkly我会按这个顺序验证我不会一上来就把现有 Agent 流程全部迁移过去。这类项目最容易出现的问题是“看着很厉害真跑起来才发现连最小闭环都没打通”。所以我会先做三个实验。3.1 第一步最小闭环一条 prompt 从编译到运行先写一个最小流程一个输入字段一次模型调用一个结构化输出字段。下面的结构只是示意不代表 Linkly 的真实语法pipeline hello { input: { name: string } stage call_model { model: your-model-id prompt: Say hello to {{input.name}} output_type: { greeting: string } } }这一步要验证的核心问题只有两个语言能不能被解析编译流程能不能把高层结构送到对应的运行时去执行。如果最小闭环跑通了说明工具链的基础部分是可用的。如果这一步都磕磕绊绊那后续的 MLIR pass、后端降级大概率还很早期。同时我会特别留意编译产物长什么样。如果它真的通过 MLIR 编译应该能看到某种 IR 结构而不是直接生成一个 Python 函数。这一步能帮助判断“MLIR”在项目里到底承担了真实角色还是只在某个边角做了拼接。先跑通最小闭环再谈复杂流程。这句话放在普通工程里是常识放在 LLM 编译器项目里更是如此。3.2 第二步试分支、工具调用、结构化输出和重试最小闭环之后我会把复杂度慢慢加上去。重点测试四类能力分支、工具调用、多阶段数据依赖、失败重试。下面是一个我自己常用的验证表不针对 Linkly 的具体实现只代表这类流程编译语言最该被考验的几个点测试项想验证的问题通过标准分支是否支持不同输入走向不同模型或 prompt编译期能识别分支条件运行期分支结果正确工具调用能否把外部工具声明为编译期可感知的节点工具入参与返回值能参与类型检查多阶段依赖上游输出能否直接作为下游输入类型不匹配时在编译或链接阶段告警失败重试语言如何表达“运行时模型挂了”重试策略明确不会出现无限循环或静默失败如果 Linkly 目前还不支持全部能力我会记录它的边界。因为语言早期往往先支持一条直线链路分支、循环、工具调用这些控制流能力才真正区分“演示项目”和“可用语言”。如果最小闭环跑通了但运行结果总是不对我会按这个顺序排查先看 DSL 源码层的数据契约有没有写对。再看生成的 IR 是否还保留了每个 stage 的名称和数据流关系。再看编译期告警发生在哪个 pass是类型问题还是流程结构问题。再看模型原始返回是否真的符合声明的 output_type模型可能返回了字符串但被误判成 JSON。最后看后端适配层超时、重试、JSON 模式、temperature 都可能影响最终结果。这个顺序不能反。大部分看似运行时的问题根因往往在最外层的数据契约和后端适配而不是出在 IR 优化上。3.3 第三步换一个后端看 DSL 是否真的与执行端解耦编译到 MLIR 的一个很大卖点是同一种高层描述可以降级到不同后端。如果 Linkly 真的把这条路打通了那么从云端模型切换到本地模型或者从模型 A 换到模型 B理想情况下应该只需要改动一个后端配置而不是重写整段 DSL。我会专门做一次“换后端”实验。比如同一个 pipeline先跑某个模型服务再尝试切换到本地推理引擎观察哪些部分需要改哪些部分不需要改。这个实验很能暴露问题。有些项目只是把 API 请求封装成了自己的 DSL底层和某一个供应商强绑定。那样的话MLIR 编译更像是一个包装壳并没有带来真正的后端无关性。而真正有价值的编译式流程应该让高层流程逻辑保持稳定只有 target 部分在变化。4. 真正难的不是编译而是让编译器接受模型的不确定性4.1 编译期能保证结构不能保证语义即便解决了解析、类型检查、IR 降级、后端适配LLM 编译器仍然要面对一个本质问题大模型输出在语义上不可预测。你可以声明一个output_type: { keywords: string[] }然后让模型返回一个 JSON 数组。结构上可能完全正确但数组里可能是一个空列表可能是和问题无关的词甚至可能是看似合理但实际错误的幻觉结果。编译器能检查格式却不能保证“内容正确”。所以 Linkly 这类项目不能把话说得太满。它最合适的定位不是“用编译器消灭所有运行时错误”而是“用编译器减少那些本来可以提前发现的错误”。4.2 LLM 应用的三座大山会直接影响编译器的边界第一座大山是输入不可控。用户输入是自然语言可能有错别字、歧义、超长文本也可能被刻意注入不良指令。编译器无法像处理强类型语言的整型变量一样假设输入一定落在某个枚举范围内。第二座大山是输出不稳定。同一个模型、同一个 prompt在不同 temperature 下会给出不同结果。即使把 temperature 设为 0也不能保证结果在语义层面稳定。输出 schema 可以解决格式但不能解决内容的可信度。第三座大山是上下文有限。一次模型调用能携带的 token 数有限RAG 检索结果要不要截断、历史会话保留多少、哪些内容优先进入 context这些是运行时动态决策。编译期也许能表达“上下文的来源”但很难在所有场景里静态决定“这次到底该放哪些内容”。用一张表总结一下问题编译期能做运行时仍需做上下游字段类型不匹配提前发现无需再处理模型返回格式不符合 schema无法完全避免动态校验、修复、重试模型内容幻觉或语义偏差无法解决使用规则、人工复核、多模型投票上下文超限可以做上限检查截断、摘要、动态选择检索内容4.3 更务实的组合静态编译器 动态护栏我认为 Linkly 这类项目最可能的成熟形态不是把 LLM 流程完全静态编译掉而是做一套“静态保障 动态护栏”的组合。编译器负责在运行前处理可以确定的事情比如输入输出 schema 的一致性分支条件的可达性工具调用的参数类型不必要的重复流程上下文预算的静态上限运行时负责处理无法静态确定的事情比如模型返回格式错乱时的修复重试和降级内容安全过滤语义相似度校验人工审核入口如果 Linkly 能在 IR 层提供类似llm.output_check、llm.retry、llm.fallback这样的原语让开发者把“动态护栏”也写进程序里那它的价值会比单纯做“语法糖”大得多。因为这样一来校验和重试不再是散落在 Python 函数里的样板代码而成为编译流程中可以分析、可以测试、可以复用的逻辑。5. 我的判断Linkly 的价值不在“少写代码”而在让流程变成可分析对象5.1 核心不是语法而是编译视角如果 Linkly 最终只是发明了一套新的 prompt 书写格式那它很难长远。真正的价值在于“编译视角”——把 LLM 应用当成一个可以被分析的软件系统而不是一段可以运行的提示词集合。这个视角改变了很多默认假设。过去我们会问“这个提示词写得对不对”更成熟的问法变成“这个流程的数据契约是否完整”“上下游类型是否匹配”“失败后有没有兜底”“换一个模型后端流程还能不能复用”当这些问题被结构化地提出后LLM 应用开发就不再只能依赖个人经验而是可以像普通软件工程一样拥有类型、检查、测试、代码评审和版本演进。语言和编译器在这里不是噱头而是把经验固化成工具的载体。5.2 什么时候值得把流程“编译化”并不是所有 LLM 应用都需要从框架切到 DSL更不是所有团队都应该自己维护编译器。下面是一个粗略判断框架情况建议只是写一次性的脚本跑完就丢不需要 DSL不需要编译器一个流程要长期维护且频繁改模型值得考虑编译式 DSL多阶段之间有复杂的输入输出契约编译器能明显降低维护成本团队小且没有编译器基础先使用成熟编排框架边界清晰后再迁移场景需要审计、复现、多版本回溯编译式和 IR 日志会带来很大优势我会尤其推荐关注第二种和第五种情况。LLM 应用最大的隐性成本不是第一次跑通的时间而是后续每次改动带来的不确定性。编译式流程把“改动”变成一次可检查、可构建、可回滚的过程这个优势在长期的业务里会越来越明显。5.3 给想尝鲜的人三条建议第一不要急着把整个 Agent 系统搬到 Linkly 上。先挑一个边界最清楚、输入输出最稳定、你每天都在改的流程做一次小范围实验。第二认真记录三层对应关系源码、编译后的 IR、运行时日志。如果项目还处在早期这个对应关系是否清晰直接决定了它的开发体验。对应关系越清晰后续排错越容易。第三保持预期管理。它是一个 Show HN 项目意味着很大概率还在迭代中。你遇到 bug 时不要默认“编译器能解决一切”而要想清楚这个问题是语法问题、IR 设计问题还是模型本身的不确定性导致的。Linkly 现在未必是生产可用的工具但它指出的方向很值得长期关注LLM 应用不只缺更好的框架更缺一套能描述流程、校验契约、支撑分析和优化的软件基础设施。把一次临时的手工流程沉淀成可编译的工程制品这条路比“换一个更会写 prompt 的模型”更靠近问题的本质。如果有一天LLM 应用可以像普通程序一样在运行前就告诉你“这段流程里有一个类型错误”那大概就是这一类项目真正成功的时刻。
返回列表