尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Slang 自动微分 IR 指令族完全解析:从 `__fwd_diff` 到 `ForwardDifferentiate` 的降级路径与逐指令测试验证

Slang 自动微分 IR 指令族完全解析:从 `__fwd_diff` 到 `ForwardDifferentiate` 的降级路径与逐指令测试验证 Slang 自动微分 IR 指令族完全解析从__fwd_diff到ForwardDifferentiate的降级路径与逐指令测试验证【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang本文是 Slang 编译器自动微分automatic differentiationIR 指令族的逐指令参考指南围绕 docs/generated/design/ir-reference/differentiation.md 与对应的逐指令测试包 docs/generated/tests/design/ir-reference/differentiation/ 展开。文章覆盖差分对differential pair的构造与投影指令、前向/反向模式翻译指令、旧式桥接legacy bridge指令、detachDerivative、no_diff标记与检查点/重物化指令并给出从用户源码表面到LOWER-TO-IR转储的可观测映射。读完你将掌握每个微分指令的用户源码入口是什么、在-dump-ir转储中长什么样、哪些指令只由内部 pass 合成而无法从用户程序观察到以及如何用 FileCheck 为它们编写回归测试。一、背景自动微分在 Slang IR 中的位置Slang 的自动微分机制并不以给函数加一个求导标志的方式进入 IR而是由一组**专门的 IR 指令opcode**承载构造和投影差分对的指令、请求某个函数值前向/反向导数的指令、把历史遗留的合并式反向函数桥接到当前拆分式表示的指令以及支撑检查点checkpointing和重物化rematerialization的指令。这些指令的消费方是编译器的 autodiff pass 族slang-ir-autodiff*.cpp、翻译 passsource/slang/slang-ir-translate.cpp以及需要在部分微分完成的 IR 模块上工作的 emit 路径。这些指令被放入模块的方式有三类生产者理解它们有助于判断某条指令能否被用户代码直接触发核心模块core module所有差分对指令与detachDerivative的源头。DifferentialPairT在 source/slang/core.meta.slang 中声明其__init通过__intrinsic_op($(kIROp_MakeDifferentialPair))第 786 行绑定构造指令p/v/d属性分别绑定DifferentialPairGetPrimal/DifferentialPairGetDifferential第 791、797、803 行DifferentialPtrPairT声明对应的指针风味四件套第 877-894 行。自由函数diffPair(primal, diff)source/slang/diff.meta.slang 第 1329 行是MakeDiffPair的第二种拼写detachT(T x)第 2081 行是detachDerivative的源头。语义检查semantic checking大多数TranslateBase指令的来源。SynthesizedFuncDeclsource/slang/slang-ast-decl.h 第 760 行携带一个uint32_t irOp字段和ListVal* operands检查一个可微调用时source/slang/slang-check-decl.cpp 会合成这类 decl 并把目标指令码写进irOp。降级时 source/slang/slang-lower-to-ir.cpp 第 13858 行读取该字段、逐一降级每个Val操作数调用emitIntrinsicInst生成指令。autodiff pass 自身其余指令。核心消费者是TranslationContext::maybeTranslateInstsource/slang/slang-ir-translate.cpp 第 40 行它把每条TranslateBase指令解析为具体的函数或 witness table并把结果记忆化在模块的翻译字典中逐指令的工作分布在 slang-ir-autodiff-fwd.cpp、slang-ir-autodiff-rev.cpp、slang-ir-autodiff-unzip.cpp、slang-ir-autodiff-transpose.cpp、slang-ir-autodiff-primal-hoist.cpp 中。注意当前提交点没有slang-ir-autodiff-transcribe.cpp前向模式转录代码位于slang-ir-autodiff-fwd.cpp内。关键事实本指令族没有任何一条会生成目标代码。finalizeAutoDiffPasssource/slang/slang-ir-autodiff.cpp 第 1174 行运行processPairTypes把MakeDiffPair改写成MakeStruct、把两个投影改写成字段访问随后执行removeDetachInsts与removeTypeAnnotationslowerDiffTypeInfoInstssource/slang/slang-emit.cpp 第 882 行会若存在把DiffTypeInfo改写成makeTuple。两者都以requiredLoweringPassSet.autodiff为门控而calcRequiredLoweringPassSet同文件第 405 行只要看到任何IRTranslateBase、IRTranslatedTypeBase、IRDifferentialPairTypeBase或IRMakeDifferentialPairBase指令就会置位。唯一的例外是builtinRequirementKey它可提升hoistable可能在特化后作为未被引用的全局存活而 source/slang/slang-emit-c-like.cpp 第 5338 行的ensureGlobalInst会显式跳过这条纯元数据指令。二、指令族谱系Family hierarchy整个微分指令族以五个抽象组为骨架见 docs/generated/design/ir-reference/differentiation.md 的谱系图MakeDifferentialPairBase→MakeDiffPair、MakeDiffRefPairDifferentialPairGetDifferentialBase→GetDifferential、GetDifferentialPtrDifferentialPairGetPrimalBase→GetPrimal、GetPrimalRefTranslateBase→ 前向算子、反向算子、旧式桥接算子、合成导数 witness未分组指令 →checkpointObj、loopExitValue、ReportCheckpointStore、四个占位符LoadReverseGradient、ReverseGradientDiffPairRef、PrimalParamRef、DiffParamRef、detachDerivative、DiffTypeInfo、builtinRequirementKeyTranslateBase是唯一拥有庞大成员的抽象组三个 pair 组的存在是为了让消费者可以用一次asIRMakeDifferentialPairBase(inst)同时匹配值与指针两种风味——calcRequiredLoweringPassSet和processPairTypes都依赖这一点。三、差分对构造MakeDiffPair与MakeDiffRefPair3.1 指令语义OpcodeC wrapper操作数标志AST 源头语义MakeDiffPairIRMakeDifferentialPairprimal, differentialDifferentialPairT.__init与diffPair(...)均为__intrinsic_op($(kIROp_MakeDifferentialPair))把值类型 primal 与其 differential 打包MakeDiffRefPairIRMakeDifferentialPtrPairprimal, differentialDifferentialPtrPairT.__init__intrinsic_op($(kIROp_MakeDifferentialPtrPair))把指针类型 primal 与其微分指针打包MakeDiffPair的结果类型是IRDifferentialPairType类型细节归 types.md 的 differentiation types 一节。它并非 autodiff 内部指令用户直接写DifferentialPairfloat(x, dx)或diffPair(x, dx)就会得到它——这正是为什么即使模块从未提及fwd_diff/bwd_diffcalcRequiredLoweringPassSet看到任何IRMakeDifferentialPairBase也会标记模块需要 autodiff 终结 pass。3.2 源码表面与降级对应测试 make-diff-pair.slang 验证了操作数顺序(primal, differential)uniform float a; uniform float d; uniform RWStructuredBufferfloat buf; [ForwardDifferentiable] float f(float x) { return x * x; } [numthreads(1, 1, 1)] void main() { buf[0] __fwd_diff(f)(DifferentialPairfloat(a, d)).d; }其 FileCheck 断言在func %main内部出现DiffPair(Float, %witness) MakeDiffPair(%a, %d)即%a在前、%d在后。两个操作数特意使用uniform全局变量目的是阻止常量折叠constant folding把构造运算塌缩掉——这是本指令族观测测试的关键技巧。另一个测试 make-diff-pair-diff-pair-free-function.slang 验证自由函数diffPair(primal, diff)与DifferentialPairT构造函数产生同一条指令。值得注意的文档缺口differentiation.md的表格把MakeDiffPair的 AST 源头标为(synthesized)但事实上用户手写的DifferentialPairT(...)构造函数在任何前向模式微分函数的调用点都会自然产生它——测试包把这一点当作可观测声明处理并把这个AST origin 列与实际不符记录为文档缺口。四、差分对投影GetPrimal/GetDifferential/GetPrimalRef/GetDifferentialPtrOpcodeC wrapper操作数语义GetDifferentialIRDifferentialPairGetDifferentialpair读取DifferentialPair的微分分量源码为.dGetDifferentialPtrIRDifferentialPtrPairGetDifferential†min1读取DifferentialPtrPair的微分指针GetPrimalIRDifferentialPairGetPrimalpair读取DifferentialPair的 primal 分量源码为.p/.vGetPrimalRefIRDifferentialPtrPairGetPrimal‡ptrPair读取DifferentialPtrPair的 primal 指针‡ 表示 C wrapper 为手写而非由 Lua 条目生成† 表示 Lua 条目只声明了min_operandswrapper 没有具名访问器消费者直接用getOperand(i)。四个投影是MakeDiffPair的逆操作在 IR 中由前向模式转录器在只需要某个 pair 的一个分量时发出。源码层面它们就是DifferentialPairT的.d与.p/.v属性 getter。lowerPairAccesssource/slang/slang-ir-autodiff-pairs.cpp 第 480 行把全部四个投影改写成降级后 pair 结构体上的字段访问lowerMakePair第 448 行把两个构造函数改写成makeStruct两者都由processPairTypes第 592 行驱动。在改写之前没有任何 pass 会把GetPrimal(MakeDiffPair(a, b))折叠回a。一个有趣的实现细节四个投影对同一逻辑角色的操作数命名不一致——GetDifferential/GetPrimal命名为pairGetPrimalRef命名为ptrPairGetDifferentialPtr没有命名。实际使用中全部通过抽象基类的getBase()访问。测试 get-primal.slang 用CHECK-COUNT-2断言两个.p/.vgetter 产生两条Float GetPrimal(...)行证明.v是.p的别名而非独立指令get-primal-ref-get-differential-ptr.slang 覆盖指针风味。测试 get-primal-of-make-diff-pair-not-folded.slang 则验证投影不会被折叠回构造操作数这一行为。五、微分算子请求而非结果TranslateBase下的所有指令都是请求request而非结果。每条请求由TranslationContext::maybeTranslateInst解析首先查询模块的翻译字典IRCompilerDictionary由initializeTranslationDictionary创建并记忆化产物因此每条请求每个模块最多翻译一次。去重以请求本身opcode 操作数为键所以决定身份的是基础函数而不是调用点buf[0] __fwd_diff(f)(DifferentialPairfloat(a, d)).d; buf[1] __fwd_diff(f)(DifferentialPairfloat(d, a)).p;无论__fwd_diff(f)写多少次、每次传什么参数都降级为同一条let %fwd ForwardDifferentiate(%f)两个call指令共享它。5.1 前向模式Opcode操作数标志AST 源头语义ForwardDifferentiatebaseFnHForwardDifferentiateExpr__fwd_diff(...)经visitForwardDifferentiateExpr也有ForwardDifferentiateVal请求函数值的前向模式JVP导数TrivialForwardDifferentiate†min1HSynthesizedFuncDeclfwd_diff由checkDifferentiableCallableCommon为[TreatAsDifferentiable]/[HasTrivialForwardDerivative]创建请求一个运行 primal 并返回零输出微分、忽略传入切向量的导数ForwardDifferentiatePropagate†min1H无 AST 源头——由 slang-ir-autodiff-unzip.cpp 第 419 行发出解压反向模式函数体时使用的前向模式 propagate 函数H 表示 hoistable即相同请求会去重为单一 IR 值。ForwardDifferentiate(baseFn)是用户唯一能直接从表达式产生的微分算子__fwd_diff(f)解析为ForwardDifferentiateExprvisitForwardDifferentiateExprsource/slang/slang-lower-to-ir.cpp 第 5878 行发出指令把ForwardDifferentiateVal存入 witness table 时也会发出第 2492 行。测试 forward-differentiate.slang 锚定let %FWD : Func(DiffPair(Float, %w), DiffPair(Float, %w)) ForwardDifferentiate(%f)——对float - float单参数函数结果类型正是对差分对签名的 JVP 函数类型——并要求func %main内的call恰好指向该值。测试 forward-differentiate-hoisted-dedupe.slang 用CHECK-NOT: ForwardDifferentiate(%f)夹在两个call [[FWD]](之间验证两个语法上独立的__fwd_diff(f)表达式只产生一个 IR 值并把断言用### LOWER-TO-IR:与###边界限定在 LOWER-TO-IR 转储段内后面的逐 pass 转储会重复整个模块。5.2 反向模式反向模式的完整指令集包括根请求BackwardDifferentiate以及三个函数值相位BackwardDifferentiatePrimalprimal 相位计算并返回 propagate 相位需要的值、BackwardDifferentiatePropagatepropagate 相位消费记录下来的上下文和输出伴随值产生输入伴随值、BackwardRemat重物化相位从最小上下文重算 primal 值而非读取完整检查点以及各自的Trivial*平凡版本。这些指令都不会到达目标代码但从它们构建的函数和类型会——并且以生成名字出现在每个源码级文本目标HLSL、GLSL、Metal、WGSL、CUDA、C中。SPIR-V 是例外其输出不为导数函数携带OpNameSPIR-V 阅读器必须按结构而非名字匹配。命名规律可被文本目标测试观测source/slang/slang-ir-autodiff-rev.cpp 的generateName给 propagate 函数前缀s_bwdProp_第 405、726 行、给完整中间上下文结构体前缀s_bwdCallableCtx_第 314、727 行于是f产生s_bwdProp_f与携带被提升 primal 状态的s_bwdCallableCtx_f前向模式则是 source/slang/slang-ir-autodiff-fwd.cpp 第 2271 行构建s_fwd_orig。对应测试 reverse-mode-emitted-name-prefixes.slang 与 forward-derivative-emitted-name-prefix.slang 逐文本目标hlsl、glsl、spirv-asm、metal、wgsl、cuda、cpp验证这些前缀。5.3 旧式桥接Legacy bridge与__bwd_diff的真实降级这是本文最值得注意的表象与实现不一致之处。用户写__bwd_diff(f)或别名bwd_diff(f)时LOWER-TO-IR 阶段实际发出的是LegacyBackwardDifferentiate而不是BackwardDifferentiate[Differentiable] float f(float x) { return x * x; } [numthreads(1, 1, 1)] void main() { DifferentialPairfloat dp DifferentialPairfloat(a, 0.0); __bwd_diff(f)(dp, 1.0); buf[0] dp.d; }降级为let %bwd : Func(Void, BorrowInOutParam(DiffPair(Float, %w)), Float) LegacyBackwardDifferentiate(%apply_bwd, %remat, %ctx_t)——三个操作数依次是 bwd 可调用函数、remat 函数和中间上下文类型。测试 legacy-backward-differentiate.slang 用 FileCheck 变量依次捕获BackwardDifferentiatePrimal(%f)、BackwardDifferentiatePropagate(%f)、BackwardRemat(%f)三个相位请求再要求LegacyBackwardDifferentiate按 apply_bwd、remat、propagate 的顺序列出它们——顺序错乱会导致测试失败。测试 bwd-diff-does-not-emit-backward-differentiate.slang 则用CHECK-NOT: BackwardDifferentiate(证明该 opcode 从未由用户表面产生前缀是为了避免被LegacyBackwardDifferentiate、TrivialBackwardDifferentiate等含子串的拼写误触发。现代的BackwardDifferentiate指令由 unzip pass 在管线后期把旧式形式转换而来。这个文档把__bwd_diff列为BackwardDifferentiate的 AST 源头但 LOWER-TO-IR 实际发LegacyBackwardDifferentiate的差异被测试包如实记录为文档缺口。桥接族的其余指令BackwardFromLegacyBwdDiffFunc与三个投影BackwardPrimalFromLegacyBwdDiffFunc、BackwardRematFromLegacyBwdDiffFunc、BackwardPropagateFromLegacyBwdDiffFunc共享(targetFunc, legacyBwdDiffFunc)操作数对各自从解析出的五元组中取一个元素测试 legacy-bridge-projections.slang 覆盖。LegacyBackwardDifferentiate不遵循该形状它携带三个函数由 source/slang/slang-ir-autodiff-rev.cpp 第 759-761 行的maybeTranslateLegacyBackwardDerivative作为操作数 0、1、2 读取。5.4 合成导数 witness 与实验性__func_extensionFunctionCopy、SynthesizedForwardDerivativeWitnessTable、SynthesizedBackwardDerivativeWitnessTable、MakeIDifferentiableWitness、IdentityRemat等指令服务于为高阶导数合成 witness table与把已有函数命名位合成导数成员的函数体。其中IdentityRemat背后的__func_extension表面对应_funcExtensionApply/_funcExtensionBackwardDiff源头是实验特性除非传入-experimental-featurevisitFuncExtensionDeclsource/slang/slang-check-decl.cpp 第 16269-16275 行会对任何__func_extension声明给出诊断 W30131func-extension-requires-experimental-feature。注意这是一个警告而不是错误——声明会被丢弃编译不会立即停止而是在后续引用该声明处才以另一个更不明显的错误失败。核心模块的元代码享有豁免这正是它能用同一语法附加条件导数 witness 的原因因此IdentityRemat完全没有非实验性的用户表面。对应测试 func-extension-requires-experimental-feature.slang 以诊断形式观测这一点。六、detachDerivative与no_diff的两种形态detachDerivative(value)原样返回操作数但阻断导数穿过它传播——对 autodiff 系统而言该值表现为常量。它有两条源码拼写核心模块detachT(T x)内建函数。测试 detach-derivative.slang 在[Differentiable]函数体内写return x * detach(x)断言let %DET : Float detachDerivative(%x)且随后 mul(%x, [[DET]])——同一个值既有 detached 又有未 detached 的使用。注意 opcode 拼写是小写 ddetachDerivative不是DetachDerivative。对表达式应用no_diff/[TreatAsDifferentiable]。visitTreatAsDifferentiableExprsource/slang/slang-lower-to-ir.cpp 第 5890 行会把物化后的IRLoad包进detachDerivative使no_diff在局部数组索引与资源索引上行为一致见测试 no-diff-expression-detach.slang。但no_diff用在参数声明上是完全不同的构造不会产生任何detachDerivative指令。在那里它是修饰符而非表达式检查阶段把NoDiffModifierVal从参数类型移到ParamDecl上成为NoDiffModifiersource/slang/slang-check-decl.cpp 第 6766-6781 行普通 var decl 的同样移动在第 2876-2878 行可微性查询从任一位置读取它doesTypeHaveNoDiffModifier第 5337、5338 行。测试 no-diff-param-produces-no-detach.slang 验证了边界函数scaleBy(float x, no_diff int k)被微分时LOWER-TO-IR 段内CHECK-NOT: detachDerivative取而代之的是模块作用域let %ND : Void no_diff与入口块上的param %k : Attributed(Int, %ND)——no_diff作为类型的一部分存活而不是作为指令。差分完成后removeDetachInsts由finalizeAutoDiffPass调用会删除所有detachDerivative。七、五元组翻译机制相位由元组下标携带反向模式的精髓在于单一根请求BackwardDifferentiate翻译为makeTuple的五个元素——primal 函数、remat 函数、propagate 函数、完整中间上下文类型、最小上下文类型。而BackwardDifferentiatePrimal、BackwardRemat、BackwardDifferentiatePropagate、BackwardDiffIntermediateContextType、BackwardDiffMinimalContextType各自通过为同一基础函数合成一个单操作数BackwardDifferentiate、翻译之、再返回元组元素 0 到 4 来解析source/slang/slang-ir-translate.cpp 第 164-198 行。Trivial*族第 199-237 行与*FromLegacyBwdDiffFunc族第 244-287 行以TrivialBackwardDifferentiate与BackwardFromLegacyBwdDiffFunc为根遵循同一形状。这就是各相位指令除基础函数外不需要任何操作数的原因相位由元组下标携带。两个上下文类型属于 Type 族归 types.md 的BackwardDiffIntermediateContextType一节尽管解析发生在与函数值相位相同的 switch 中。TrivialBackwardDifferentiate与BackwardFromLegacyBwdDiffFunc永远不来自 AST 声明——它们只是翻译 pass 为自己构建的临时根。BackwardDifferentiate的操作数形状值得小心Lua 条目声明min_operands 3手写 wrapper 声明getApplyFunc()、getContextType()、getBwdPropFunc()读取操作数 0、1、2——但当前提交点所有生产者都只构建一个操作数。IRBuilder::emitBackwardDifferentiateInstsource/slang/slang-ir.cpp 第 3759 行只传单个baseFn翻译 pass 合成的根也只传一个操作数。三个访问器没有任何调用者。min_operands变为IROpInfo::fixedArgCountsource/slang/slang-ir.h 第 101 行而没有任何代码读取它。此外用户可见的__bwd_diff形式根本到不了 IR语义检查更早地解析了BackwardDifferentiateExprvisitBackwardDifferentiateExprsource/slang/slang-lower-to-ir.cpp 第 5972 行直接调用SLANG_UNEXPECTED。[PrimalSubstitute]/[PrimalSubstituteOf]同理——它们是 source/slang/diff.meta.slang 中声明的 AST 级属性不存在PrimalSubstituteopcode测试 primal-substitute-has-no-opcode.slang 覆盖。八、内置需求键builtinRequirementKey与BuiltinRequirementDecorationIDifferentiable/IBackwardDifferentiable/IBwdCallable接口被编译器识别autodiff pass 需要按角色BuiltinRequirementKind::DifferentialType、DAddFunc、DifferentialWitness……而不是按条目位置查找需求。builtinRequirementKey是承载该角色的可提升需求键指令BuiltinRequirementDecoration是查找辅助扫描的配套装饰。与普通key/StructKey每个需求声明一个独立全局符号靠key_mangled链接名跨模块统一不同内置键可提升且按 kind 操作数去重——同一逻辑需求无论从规范接口约束、构建类型Differential时合成的约束、还是跨预编译核心模块边界引用都解析为同一个键指令且不需要任何链接装饰因为身份来自操作数。getInterfaceRequirementKeysource/slang/slang-lower-to-ir.cpp 第 1726 行从需求的BuiltinRequirementModifier计算角色——例如当被建键的是关联一致性而非关联类型时把DifferentialType提升为DifferentialWitness——然后调用getBuiltinRequirementKey并附加BuiltinRequirementDecoration第 1814-1820 行。getInterfaceEntryByBuiltinRequirementsource/slang/slang-ir-autodiff.cpp 第 229 行扫描该装饰按角色找条目。由于内置需求可能经动态分派到达GetDispatcher的lookupKey操作数被类型化为普通IRInst而非IRStructKey见 source/slang/slang-ir-insts.lua 第 3222-3225 行注释。相关测试包括 builtin-requirement-decoration.slang键与装饰携带同一 kind、builtin-requirement-key-deduped.slang可提升与按构造去重与 builtin-requirement-key-differential-witness.slang角色提升产生不同键。九、检查点与重物化checkpointObj、loopExitValue、ReportCheckpointStore反向模式 autodiff 经常需要在 primal 值不再自然存活的位置使用它这些指令标记候选值让 primal-hoisting pass 在保持值存活与重算它之间决策checkpointObj(value)自身不存储任何东西它制作一个值的独立副本使循环体内与循环体外的使用可以被独立提升——这正是它包裹的 primal 值在 primal-hoisting pass 决定检查点还是重算之前所需要的。unzip pass 用它包裹 primal 返回值与最小上下文值source/slang/slang-ir-autodiff-primal-hoist.cpp 第 2778 行消费它。loopExitValue记录 SSA 变量在循环出口的值供反向模式读取由 slang-ir-autodiff-primal-hoist.cpp 第 393 行发出。ReportCheckpointStore(storedType, originalFunc, storeRef)是同一机制的诊断通道只有传入-report-checkpoint-intermediatessource/slang/slang-options.cpp 第 622 行才会打印。报告是 id 为-1的standalone_notesource/slang/slang-diagnostics.lua 第 5916 行首行点名originalFunc与总大小note: checkpointing context of 8 bytes associated with: f随后每个被存值一个可变参数 span渲染为size bytes (typeName)同一类型多个实例时渲染为count instances of size bytes (typeName)——storedType操作数就在这里显现。其storeRef操作数是弱引用若 store 后被消除操作数变为Poisonsource/slang/slang-emit.cpp 第 271 行的报告遍历会跳过该条目。DCE 在isWeakReferenceOperandsource/slang/slang-ir-dce.cpp 第 652 行中赋予该操作数弱引用地位被删除的 store 以 poison 替换而不是被标记器拖住存活标记器本身则靠shouldInstBeLiveIfParentIsLive第 531 行中保守的mightHaveSideEffects测试存活最后由报告遍历移除。测试 report-checkpoint-store-diagnostic.slang 观测该报告。十、无法从用户表面观测的指令Untested claims以下指令在文档中标注为(synthesized)只由内部 autodiff IR pass 产生在 LOWER-TO-IR 阶段没有任何自然用户表面。观测它们需要检查后期 pass 的 IR 转储超出了逐指令参考测试包的范围因此被记录在测试包 README 的## Untested claims一节而非写成测试MakeDiffRefPair、GetDifferentialPtr、GetPrimalRef——指针风味的构造与投影用于 autodiff pass 内部管线。ForwardDifferentiatePropagate、TrivialForwardDifferentiate——由 unzip / 转录管线合成__fwd_diff在 LOWER-TO-IR 不产生它们。BackwardDifferentiate本身——文档把__bwd_diff列为它的 AST 源头但 LOWER-TO-IR 实际发LegacyBackwardDifferentiate现代指令在 LOWER-TO-IR 阶段视为超范围。BackwardDifferentiatePrimal/BackwardDifferentiatePropagate/BackwardRemat/TrivialBackwardDifferentiate*——由 unzip pass 产生。四个旧式桥接提取指令BackwardFromLegacyBwdDiffFunc族——合成。FunctionCopy、SynthesizedForwardDerivativeWitnessTable、SynthesizedBackwardDerivativeWitnessTable、MakeIDifferentiableWitness等导数 witness 合成指令——内部。LoadReverseGradient、ReverseGradientDiffPairRef、PrimalParamRef、DiffParamRef——autodiff 临时占位符不会存活过拆分/反向传播 pass。DiffTypeInfo——合成类型信息容器。checkpointObj、loopExitValue、ReportCheckpointStore——由反向模式 pass 插入的检查点标记LOWER-TO-IR 无表面形态。测试包 README 的 Untested claims 表格为每条给出了理由分类implementation-detail实现细节任何 Slang 程序都无法区分、internal-source-fact关于编译器源码结构的陈述、link-stage-only只存在于链接/翻译阶段晚于 LOWER-TO-IR 观测点、unsupported-on-targetSPIR-V 汇编无OpName生成名字不可观测。例如四个占位符各有IRBuilder辅助函数source/slang/slang-ir-insts.h 第 4144-4147 行声明source/slang/slang-ir.cpp 第 5503-5541 行定义但在source/中没有任何调用者LoadReverseGradient与ReverseGradientDiffPairRef仍出现在两个消费者 switch 中slang-ir-autodiff-transpose.cpp 第 1569 行、IRInst::mightHaveSideEffects第 9456 行而PrimalParamRef/DiffParamRef既无生产者也无消费者。DiffTypeInfo有消费者无生产者__hasDiffTypeInfo(T)解析为HasDiffTypeInfoConstraintDecl并求解为HasDiffTypeInfoWitness但该 witness 降级为 void 值、约束发出 void 参数slang-lower-to-ir.cpp 第 2670 行所以DiffTypeInfo指令永远不会被构建测试 has-diff-type-info-constraint-void-param.slang。SynthesizedBackwardDerivativeWitnessTableFromLegacyBwdDiffFunc最彻底其稳定名字字符串在source/中除 Lua 定义与稳定名表外无处出现。十一、逐指令测试方法论从声明到可观测断言测试包的标准观测形式定义于 docs/generated/tests/design/ir-reference/differentiation/_prompt.md是一条//TEST指令//TEST:SIMPLE(filecheckCHECK):-target spirv-asm -dump-ir -o - -entry main -stage compute三条通用规则缺一不可也是测试作者反复踩坑后沉淀的教训-dump-ir必须搭配-target 文本目标和-o -否则 IR 转储会被目标文本污染无法干净地送到 stdout。pipeline_stagelower——这些是 LOWER-TO-IR 观测。CHECK 模式锚定用户命名符号func %main、func %f、let %fwd_diff、let %bwd_diff。autodiff 转储在用户 IR 之前有一个非常大的前导每个IDifferentiable/IForwardDifferentiable/IBackwardDifferentiable接口、每个 key、每类型内建 witness不锚定用户符号的话前导就可能意外满足模式。另有四条观测纪律输出必须逃逸 DCE写入RWStructuredBufferT纯内部计算会在转储前被移除测试 pair-opcodes-produce-no-target-code.slang 同时演示了pair 在 emit 时被溶解它对 hlsl、glsl、spirv-asm、metal、wgsl、cuda、cpp 七个目标各断言{{GetPrimal|GetDifferential|DiffPair}}不出现并用COMPARE_COMPUTE -cpu验证p.p与p.d的值 3.0 / 6.0 在改写后原样存活。MakeDiffPair/detach的操作数必须非恒定用uniform全局否则常量折叠会塌缩运算。不断言 C wrapper 结构体身份IRMakeDifferentialPair、IRForwardDifferentiate等——转储显示的是驼峰 opcode 拼写MakeDiffPair、ForwardDifferentiate、GetPrimal、GetDifferential。每个测试函数必须标注[ForwardDifferentiable]前向观测或[BackwardDifferentiable]/[Differentiable]反向观测否则对__fwd_diff(f)的调用是检查器错误根本不发 IR。测试包的期望结构1 个README.md含## Untested claims收纳所有 slangc 不可观测项 10 到 18 个.slang测试文件每个文件聚焦一条可观测 opcode或一条可观测的表面到 opcode 降级映射紧密相关的观测归入同一文件。README 的## Functional coverage表格把每条可观测声明C1–C115映射到意图类型functional / boundary / negative / expansion、文档锚点与具体测试文件## Doc gaps observed则如实记录文档与实现的差异。十二、文档缺口速览Doc gaps测试包 README 记录了五类已确认的文档缺口阅读differentiation.md时值得留意BackwardDifferentiate的 AST 源头误导反向模式表格给出BackwardDifferentiateVal源头但正文随即说__bwd_diff根本不到 IR——读者无从得知__bwd_diff(f)到底变成了什么答案是合成成员上的LegacyBackwardDifferentiate值。no_diff双义未警示文档列出两条detachDerivative源码拼写但未警告参数声明上的no_diff是不同构造、不产生任何指令对应测试 no-diff-param-produces-no-detach.slang 已钉死该边界。合成入口点未映射到用户属性AST 源头列命名内部检查入口checkDifferentiableCallableCommon、trySynthesizeDiffFuncRequirementWitness等而不命名触发它们的用户级属性[Differentiable]/[BackwardDifferentiable]、[TreatAsDifferentiable]、[BackwardDerivative(f)]、__func_extension __apply。去重缺少示例文档说相同翻译请求去重为单一 IR 值但无示例forward-differentiate-hoisted-dedupe.slang正是为补上这个示例而存在。processPairTypes改写缺少目标文本说明改写后的结构体只在微分调用跨函数边界传 pair时存活到目标文本纯局部 pair 会在 emit 前被标量化掉。十三、结语Slang 的自动微分不是黑盒变换而是一张由指令、生产者、消费者和观测规则构成的精确图谱用户表面的DifferentialPairT(p, d)、.p/.d、__fwd_diff(f)、__bwd_diff(f)、detach(x)、no_diff各自映射到MakeDiffPair、GetPrimal/GetDifferential、ForwardDifferentiate、LegacyBackwardDifferentiate、detachDerivative与Attributed(T, %no_diff)而现代反向模式的五个相位由翻译字典中一次makeTuple的下标携带。理解哪些指令可从源码观测、哪些只由内部 pass 合成这一分层是阅读 autodiff pass 源码source/slang/slang-ir-autodiff-*.cpp、调试部分微分 IR 模块或为指令族新增回归测试的起点——逐指令测试包 docs/generated/tests/design/ir-reference/differentiation/ 提供了可直接复用的观测模板与纪律。进一步阅读指令模式与新增 opcode工作流见 ir-instructions.md差分对与上下文类型见 types.mdautodiff 设计动机见 autodiff.md。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表