Enzyme main
Loading...
Searching...
No Matches
ExpandImpulsePass.cpp
Go to the documentation of this file.
1//===- ExpandImpulsePass.cpp - Expand Impulse region ops ----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a pass that expands high-level Impulse region ops
10// (simulate, generate, regenerate, mcmc, mh, untraced_call) into lower-level
11// Impulse ops (sample, random, etc.) plus arith/math/cf.
12//===----------------------------------------------------------------------===//
13
15#include "Dialect/Ops.h"
16#include "Interfaces/HMCUtils.h"
18#include "PassDetails.h"
19#include "Passes/Passes.h"
20
21#include "mlir/Dialect/Func/IR/FuncOps.h"
22#include "mlir/Dialect/Math/IR/Math.h"
23#include "mlir/IR/Builders.h"
24#include "mlir/IR/PatternMatch.h"
25#include "mlir/Interfaces/FunctionInterfaces.h"
26#include "mlir/Pass/PassManager.h"
27#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
28
29#include "llvm/ADT/APFloat.h"
30
31#define DEBUG_TYPE "expand-impulse"
32
33using namespace mlir;
34using namespace mlir::enzyme;
35using namespace mlir::impulse;
36
37namespace mlir {
38namespace enzyme {
39#define GEN_PASS_DEF_EXPANDIMPULSEPASS
40#include "Passes/Passes.h.inc"
41} // namespace enzyme
42} // namespace mlir
43
44namespace {
45
46static int64_t computeTensorElementCount(RankedTensorType tensorType) {
47 int64_t elemCount = 1;
48 for (auto dim : tensorType.getShape()) {
49 if (dim == ShapedType::kDynamic)
50 return -1;
51 elemCount *= dim;
52 }
53 return elemCount;
54}
55
56using SampleOpMap = DenseMap<Attribute, impulse::SampleOp>;
57
58static SampleOpMap buildSampleOpMap(FunctionOpInterface fn) {
59 SampleOpMap map;
60 fn.walk([&](impulse::SampleOp sampleOp) {
61 if (auto symbol = sampleOp.getSymbolAttr())
62 map[symbol] = sampleOp;
63 });
64 return map;
65}
66
67static impulse::SampleOp findSampleBySymbol(const SampleOpMap &map,
68 Attribute targetSymbol) {
69 auto it = map.find(targetSymbol);
70 return it != map.end() ? it->second : nullptr;
71}
72
73static int64_t computeSampleElementCount(Operation *op,
74 impulse::SampleOp sampleOp) {
75 int64_t totalCount = 0;
76 for (unsigned i = 1; i < sampleOp.getNumResults(); ++i) {
77 auto resultType = sampleOp.getResult(i).getType();
78 auto tensorType = dyn_cast<RankedTensorType>(resultType);
79 if (!tensorType) {
80 op->emitError("Expected ranked tensor type for sample result");
81 return -1;
82 }
83 int64_t elemCount = computeTensorElementCount(tensorType);
84 if (elemCount < 0) {
85 op->emitError("Dynamic tensor dimensions not supported");
86 return -1;
87 }
88 totalCount += elemCount;
89 }
90 return totalCount;
91}
92
93static bool computePositionSizeForAddress(Operation *op,
94 const SampleOpMap &sampleMap,
95 ArrayRef<Attribute> address,
96 SymbolTableCollection &symbolTable,
97 int64_t &positionSize) {
98 if (address.empty())
99 return false;
100
101 auto sampleOp = findSampleBySymbol(sampleMap, address[0]);
102 if (!sampleOp)
103 return false;
104
105 if (address.size() > 1) {
106 if (sampleOp.getLogpdfAttr()) {
107 op->emitError("Cannot select nested address in distribution function");
108 return false;
109 }
110
111 auto genFn = cast<FunctionOpInterface>(
112 symbolTable.lookupNearestSymbolFrom(sampleOp, sampleOp.getFnAttr()));
113 if (!genFn || genFn.getFunctionBody().empty()) {
114 op->emitError("Cannot find generative function for nested address");
115 return false;
116 }
117
118 auto nestedMap = buildSampleOpMap(genFn);
119 return computePositionSizeForAddress(op, nestedMap, address.drop_front(),
120 symbolTable, positionSize);
121 }
122
123 int64_t elemCount = computeSampleElementCount(op, sampleOp);
124 if (elemCount < 0)
125 return false;
126
127 positionSize += elemCount;
128 return true;
129}
130
131static int64_t
132computePositionSizeForSelection(Operation *op, FunctionOpInterface fn,
133 ArrayAttr selection,
134 SymbolTableCollection &symbolTable) {
135 auto sampleMap = buildSampleOpMap(fn);
136 int64_t positionSize = 0;
137
138 for (auto addr : selection) {
139 auto address = cast<ArrayAttr>(addr);
140 if (address.empty()) {
141 op->emitError("Empty address in selection");
142 return -1;
143 }
144
145 SmallVector<Attribute> tailAddresses(address.begin(), address.end());
146 if (!computePositionSizeForAddress(op, sampleMap, tailAddresses,
147 symbolTable, positionSize)) {
148 op->emitError("Could not find sample with symbol in address chain");
149 return -1;
150 }
151 }
152
153 return positionSize;
154}
155
156static int64_t
157computeOffsetForSampleInSelection(Operation *op, FunctionOpInterface fn,
158 ArrayAttr selection, Attribute targetSymbol,
159 SymbolTableCollection &symbolTable) {
160 auto sampleMap = buildSampleOpMap(fn);
161 int64_t offset = 0;
162
163 for (auto addr : selection) {
164 auto address = cast<ArrayAttr>(addr);
165 if (address.empty())
166 continue;
167
168 auto firstSymbol = address[0];
169
170 if (firstSymbol == targetSymbol) {
171 return offset;
172 }
173
174 SmallVector<Attribute> tailAddresses(address.begin(), address.end());
175 if (!computePositionSizeForAddress(op, sampleMap, tailAddresses,
176 symbolTable, offset)) {
177 return -1;
178 }
179 }
180
181 return -1;
182}
183
184static SmallVector<impulse::SupportInfo>
185collectSupportInfoForSelection(Operation *op, FunctionOpInterface fn,
186 ArrayAttr selection, ArrayAttr allAddresses,
187 SymbolTableCollection &symbolTable) {
188 auto sampleMap = buildSampleOpMap(fn);
189 SmallVector<impulse::SupportInfo> supports;
190 int64_t currentPositionOffset = 0;
191
192 for (auto addr : selection) {
193 auto address = cast<ArrayAttr>(addr);
194 if (address.empty())
195 continue;
196
197 // TODO: Handle nested cases
198 if (address.size() != 1)
199 continue;
200
201 auto targetSymbol = address[0];
202 auto sampleOp = findSampleBySymbol(sampleMap, targetSymbol);
203 if (!sampleOp)
204 continue;
205
206 auto supportAttr = sampleOp.getSupportAttr();
207
208 int64_t sampleSize = computeSampleElementCount(op, sampleOp);
209 if (sampleSize < 0)
210 continue;
211
212 int64_t traceOffset = computeOffsetForSampleInSelection(
213 op, fn, allAddresses, targetSymbol, symbolTable);
214 if (traceOffset < 0) {
215 op->emitError("Symbol in selection not found in all_addresses - cannot "
216 "determine trace offset for scattered selection");
217 return {};
218 }
219
220 supports.emplace_back(currentPositionOffset, traceOffset, sampleSize,
221 supportAttr);
222 currentPositionOffset += sampleSize;
223 }
224
225 return supports;
226}
227
228static ArrayAttr buildSubSelection(OpBuilder &builder, ArrayAttr selection,
229 Attribute targetSymbol) {
230 SmallVector<Attribute> subAddresses;
231 for (auto addr : selection) {
232 auto address = cast<ArrayAttr>(addr);
233 if (address.empty())
234 continue;
235 if (address[0] == targetSymbol && address.size() > 1) {
236 SmallVector<Attribute> tail(address.begin() + 1, address.end());
237 subAddresses.push_back(builder.getArrayAttr(tail));
238 }
239 }
240 return builder.getArrayAttr(subAddresses);
241}
242
243static int64_t
244computeOffsetForNestedSample(Operation *op, FunctionOpInterface fn,
245 ArrayAttr selection, Attribute targetSymbol,
246 SymbolTableCollection &symbolTable) {
247 auto sampleMap = buildSampleOpMap(fn);
248 int64_t offset = 0;
249
250 for (auto addr : selection) {
251 auto address = cast<ArrayAttr>(addr);
252 if (address.empty())
253 continue;
254
255 if (address[0] == targetSymbol) {
256 return offset;
257 }
258
259 SmallVector<Attribute> tailAddresses(address.begin(), address.end());
260 if (!computePositionSizeForAddress(op, sampleMap, tailAddresses,
261 symbolTable, offset)) {
262 return -1;
263 }
264 }
265
266 return -1;
267}
268
269struct ExpandImpulsePass
270 : public enzyme::impl::ExpandImpulsePassBase<ExpandImpulsePass> {
271 using ExpandImpulsePassBase::ExpandImpulsePassBase;
272
273 void runOnOperation() override;
274
275 void getDependentDialects(DialectRegistry &registry) const override {
276 mlir::OpPassManager pm;
277 mlir::LogicalResult result = mlir::parsePassPipeline(postpasses, pm);
278 if (!mlir::failed(result)) {
279 pm.getDependentDialects(registry);
280 }
281
282 registry
283 .insert<mlir::arith::ArithDialect, mlir::math::MathDialect,
284 mlir::complex::ComplexDialect, mlir::cf::ControlFlowDialect,
285 mlir::enzyme::EnzymeDialect, mlir::impulse::ImpulseDialect>();
286 }
287
288 struct LowerUntracedCallPattern
289 : public mlir::OpRewritePattern<impulse::UntracedCallOp> {
290 using mlir::OpRewritePattern<impulse::UntracedCallOp>::OpRewritePattern;
291
292 LogicalResult matchAndRewrite(impulse::UntracedCallOp CI,
293 PatternRewriter &rewriter) const override {
294 SymbolTableCollection symbolTable;
295
296 auto fn = cast<FunctionOpInterface>(
297 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
298
299 if (fn.getFunctionBody().empty()) {
300 CI.emitError("Impulse: trying to call an empty function");
301 return failure();
302 }
303
304 auto putils = ImpulseUtils::CreateFromClone(fn, ImpulseMode::Call);
305 FunctionOpInterface NewF = putils->newFunc;
306
307 SmallVector<Operation *, 4> toErase;
308 NewF.walk([&](impulse::SampleOp sampleOp) {
309 OpBuilder::InsertionGuard guard(rewriter);
310 rewriter.setInsertionPoint(sampleOp);
311
312 auto distFn =
313 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
314 sampleOp, sampleOp.getFnAttr()));
315 auto distCall =
316 func::CallOp::create(rewriter, sampleOp.getLoc(), distFn.getName(),
317 distFn.getResultTypes(), sampleOp.getInputs());
318 sampleOp.replaceAllUsesWith(distCall);
319
320 toErase.push_back(sampleOp);
321 });
322
323 for (Operation *op : toErase)
324 rewriter.eraseOp(op);
325
326 rewriter.setInsertionPoint(CI);
327 auto newCI =
328 func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
329 NewF.getResultTypes(), CI.getOperands());
330
331 rewriter.replaceOp(CI, newCI.getResults());
332
333 delete putils;
334
335 return success();
336 }
337 };
338
339 struct LowerSimulatePattern
340 : public mlir::OpRewritePattern<impulse::SimulateOp> {
341 using mlir::OpRewritePattern<impulse::SimulateOp>::OpRewritePattern;
342
343 LogicalResult matchAndRewrite(impulse::SimulateOp CI,
344 PatternRewriter &rewriter) const override {
345 SymbolTableCollection symbolTable;
346
347 auto fn = cast<FunctionOpInterface>(
348 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
349
350 if (fn.getFunctionBody().empty()) {
351 CI.emitError(
352 "Impulse: calling `simulate` on an empty function; if this "
353 "is a distribution function, its sample op should have a "
354 "logpdf attribute to avoid recursive `simulate` calls which is "
355 "intended for generative functions");
356 return failure();
357 }
358
359 ArrayAttr selection = CI.getSelectionAttr();
360 int64_t positionSize =
361 computePositionSizeForSelection(CI, fn, selection, symbolTable);
362 if (positionSize <= 0) {
363 CI.emitError("Impulse: failed to compute position size for simulate");
364 return failure();
365 }
366
367 auto putils = ImpulseUtils::CreateFromClone(fn, ImpulseMode::Simulate,
368 positionSize);
369 FunctionOpInterface NewF = putils->newFunc;
370
371 OpBuilder entryBuilder(putils->initializationBlock,
372 putils->initializationBlock->begin());
373 Location initLoc = putils->initializationBlock->begin()->getLoc();
374 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
375 auto zeroWeight =
376 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
377 DenseElementsAttr::get(scalarType, 0.0));
378 Value weightAccumulator = zeroWeight;
379
380 auto traceType =
381 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
382 auto zeroTrace =
383 arith::ConstantOp::create(entryBuilder, initLoc, traceType,
384 DenseElementsAttr::get(traceType, 0.0));
385 Value currTrace = zeroTrace;
386 int64_t currentOffset = 0;
387
388 SmallVector<Operation *> toErase;
389 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
390 OpBuilder::InsertionGuard guard(rewriter);
391 rewriter.setInsertionPoint(sampleOp);
392
393 SmallVector<Value> sampledValues; // Values to replace uses of sample op
394 bool isDistribution = static_cast<bool>(sampleOp.getLogpdfAttr());
395
396 if (isDistribution) {
397 // A1. Distribution function: call the distribution function.
398 auto distFn =
399 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
400 sampleOp, sampleOp.getFnAttr()));
401
402 auto distCall = func::CallOp::create(
403 rewriter, sampleOp.getLoc(), distFn.getName(),
404 distFn.getResultTypes(), sampleOp.getInputs());
405
406 sampledValues.append(distCall.getResults().begin(),
407 distCall.getResults().end());
408
409 auto logpdfFn =
410 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
411 sampleOp, sampleOp.getLogpdfAttr()));
412
413 // logpdf operands: (<non-RNG outputs>..., <non-RNG inputs>...)
414 SmallVector<Value> logpdfOperands;
415 for (unsigned i = 1; i < sampledValues.size(); ++i) {
416 logpdfOperands.push_back(sampledValues[i]);
417 }
418 for (unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
419 logpdfOperands.push_back(sampleOp.getOperand(i));
420 }
421
422 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
423 sampleOp.emitError("Impulse: failed to construct logpdf call; "
424 "logpdf function has wrong number of arguments");
425 return WalkResult::interrupt();
426 }
427
428 // A2. Compute and accumulate weight.
429 auto logpdf = func::CallOp::create(
430 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
431 logpdfFn.getResultTypes(), logpdfOperands);
432 weightAccumulator =
433 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
434 weightAccumulator, logpdf.getResult(0));
435
436 // A3. Check if this sample is in the selection and insert into trace
437 bool inSelection = false;
438 for (auto addr : selection) {
439 auto address = cast<ArrayAttr>(addr);
440 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
441 inSelection = true;
442 break;
443 }
444 }
445
446 if (inSelection) {
447 for (unsigned i = 1; i < sampledValues.size(); ++i) {
448 auto sampleValue = sampledValues[i];
449 auto sampleType = cast<RankedTensorType>(sampleValue.getType());
450 int64_t numElements = computeTensorElementCount(sampleType);
451 if (numElements < 0) {
452 sampleOp.emitError(
453 "Impulse: dynamic tensor dimensions not supported");
454 return WalkResult::interrupt();
455 }
456
457 auto flatSampleType = RankedTensorType::get(
458 {1, numElements}, sampleType.getElementType());
459 auto flatSample = impulse::ReshapeOp::create(
460 rewriter, sampleOp.getLoc(), flatSampleType, sampleValue);
461 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
462 auto row0 = arith::ConstantOp::create(
463 rewriter, sampleOp.getLoc(), i64S,
464 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
465 auto colOff = arith::ConstantOp::create(
466 rewriter, sampleOp.getLoc(), i64S,
467 DenseElementsAttr::get(
468 i64S, rewriter.getI64IntegerAttr(currentOffset)));
469 currTrace = impulse::DynamicUpdateSliceOp::create(
470 rewriter, sampleOp.getLoc(), traceType, currTrace,
471 flatSample, ValueRange{row0, colOff})
472 .getResult();
473 currentOffset += numElements;
474 }
475 }
476 } else {
477 // B. Generative function: recursively simulate the nested function
478 auto genFn =
479 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
480 sampleOp, sampleOp.getFnAttr()));
481
482 if (genFn.getFunctionBody().empty()) {
483 sampleOp.emitError(
484 "Impulse: generative function body is empty; "
485 "if this is a distribution, add a logpdf attribute");
486 return WalkResult::interrupt();
487 }
488
489 ArrayAttr subSelection =
490 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
491 if (subSelection.empty()) {
492 // No samples from this generative function are in the selection
493 // Just call the function directly
494 auto genCall = func::CallOp::create(
495 rewriter, sampleOp.getLoc(), genFn.getName(),
496 genFn.getResultTypes(), sampleOp.getInputs());
497 sampledValues.append(genCall.getResults().begin(),
498 genCall.getResults().end());
499 } else {
500 int64_t subPositionSize = computePositionSizeForSelection(
501 sampleOp, genFn, subSelection, symbolTable);
502 if (subPositionSize <= 0) {
503 sampleOp.emitError(
504 "Impulse: failed to compute sub-position size");
505 return WalkResult::interrupt();
506 }
507
508 // Build result types: (trace, weight, original_returns...)
509 auto subTraceType = RankedTensorType::get({1, subPositionSize},
510 rewriter.getF64Type());
511 auto scalarTy = RankedTensorType::get({}, rewriter.getF64Type());
512 SmallVector<Type> simResultTypes;
513 simResultTypes.push_back(subTraceType);
514 simResultTypes.push_back(scalarTy);
515 for (auto t : genFn.getResultTypes())
516 simResultTypes.push_back(t);
517
518 auto nestedSimulate = impulse::SimulateOp::create(
519 rewriter, sampleOp.getLoc(), simResultTypes,
520 sampleOp.getFnAttr(), sampleOp.getInputs(), subSelection);
521 auto subTrace = nestedSimulate.getTrace();
522 auto subWeight = nestedSimulate.getWeight();
523
524 weightAccumulator = arith::AddFOp::create(
525 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
526
527 int64_t mergeOffset = computeOffsetForNestedSample(
528 sampleOp, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
529 if (mergeOffset < 0) {
530 sampleOp.emitError("Impulse: failed to compute merge offset");
531 return WalkResult::interrupt();
532 }
533
534 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
535 auto row0 = arith::ConstantOp::create(
536 rewriter, sampleOp.getLoc(), i64S,
537 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
538 auto colOff = arith::ConstantOp::create(
539 rewriter, sampleOp.getLoc(), i64S,
540 DenseElementsAttr::get(
541 i64S, rewriter.getI64IntegerAttr(mergeOffset)));
542 currTrace = impulse::DynamicUpdateSliceOp::create(
543 rewriter, sampleOp.getLoc(), traceType, currTrace,
544 subTrace, ValueRange{row0, colOff})
545 .getResult();
546 currentOffset =
547 std::max(currentOffset, mergeOffset + subPositionSize);
548
549 for (auto output : nestedSimulate.getOutputs())
550 sampledValues.push_back(output);
551 }
552 }
553
554 // D. Replace uses of the original sample op with the new values.
555 sampleOp.replaceAllUsesWith(sampledValues);
556
557 toErase.push_back(sampleOp);
558 return WalkResult::advance();
559 });
560
561 for (Operation *op : toErase)
562 rewriter.eraseOp(op);
563
564 if (result.wasInterrupted()) {
565 CI.emitError("Impulse: failed to walk sample ops");
566 return failure();
567 }
568
569 // E. Rewrite the return to return (trace, weight, <original returns>...)
570 NewF.walk([&](func::ReturnOp retOp) {
571 OpBuilder::InsertionGuard guard(rewriter);
572 rewriter.setInsertionPoint(retOp);
573 SmallVector<Value> newRetVals;
574 newRetVals.push_back(currTrace);
575 newRetVals.push_back(weightAccumulator);
576 newRetVals.append(retOp.getOperands().begin(),
577 retOp.getOperands().end());
578
579 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
580 rewriter.eraseOp(retOp);
581 });
582
583 rewriter.setInsertionPoint(CI);
584 auto newCI = func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
585 NewF.getResultTypes(), CI.getInputs());
586
587 rewriter.replaceOp(CI, newCI.getResults());
588
589 delete putils;
590
591 return success();
592 }
593 };
594
595 struct LowerMCMCPattern : public mlir::OpRewritePattern<impulse::InferOp> {
596 bool debugDump;
597
598 LowerMCMCPattern(MLIRContext *context, bool debugDump,
599 PatternBenefit benefit = 1)
600 : OpRewritePattern(context, benefit), debugDump(debugDump) {}
601
602 LogicalResult matchAndRewrite(impulse::InferOp mcmcOp,
603 PatternRewriter &rewriter) const override {
604 SymbolTableCollection symbolTable;
605
606 bool hasLogpdfFn = static_cast<bool>(mcmcOp.getLogpdfFnAttr());
607
608 if (!hasLogpdfFn) {
609 auto fnAttr = mcmcOp.getFnAttr();
610 if (!fnAttr) {
611 mcmcOp.emitError("Impulse: either fn or logpdf_fn must be provided");
612 return failure();
613 }
614 auto fn = cast<FunctionOpInterface>(
615 symbolTable.lookupNearestSymbolFrom(mcmcOp, fnAttr));
616 if (fn.getFunctionBody().empty()) {
617 mcmcOp.emitError("Impulse: calling `mcmc` on an empty function");
618 return failure();
619 }
620 }
621
622 if (!mcmcOp.getStepSize()) {
623 mcmcOp.emitError("Impulse: MCMC requires step_size parameter");
624 return failure();
625 }
626
627 bool isHMC = mcmcOp.getHmcConfig().has_value();
628 bool isNUTS = mcmcOp.getNutsConfig().has_value();
629 if (!isHMC && !isNUTS) {
630 mcmcOp.emitError("Impulse: Unknown MCMC algorithm");
631 return failure();
632 }
633
634 auto loc = mcmcOp.getLoc();
635 auto invMass = mcmcOp.getInverseMassMatrix();
636 Value adaptedInvMass = invMass;
637 auto stepSize = mcmcOp.getStepSize();
638
639 auto inputs = mcmcOp.getInputs();
640 if (inputs.empty()) {
641 mcmcOp.emitError("Impulse: MCMC requires at least rng_state input");
642 return failure();
643 }
644
645 auto rngInput = inputs[0];
646
647 int64_t positionSize;
648 SmallVector<Value> fnInputs;
649 SmallVector<Type> fnResultTypes;
650 Value originalTrace;
651 ArrayAttr selection, allAddresses;
652 SmallVector<SupportInfo> supports;
653 FlatSymbolRefAttr logpdfFnAttr;
654
655 if (hasLogpdfFn) {
656 logpdfFnAttr = mcmcOp.getLogpdfFnAttr();
657 fnInputs.assign(inputs.begin() + 1, inputs.end());
658 auto initialPos = mcmcOp.getInitialPosition();
659 auto initPosType = cast<RankedTensorType>(initialPos.getType());
660 positionSize = initPosType.getNumElements();
661 selection = mcmcOp.getSelectionAttr();
662 allAddresses = mcmcOp.getAllAddressesAttr();
663 } else {
664 fnInputs.assign(inputs.begin() + 1, inputs.end());
665 originalTrace = mcmcOp.getOriginalTrace();
666 selection = mcmcOp.getSelectionAttr();
667 allAddresses = mcmcOp.getAllAddressesAttr();
668
669 auto fn = cast<FunctionOpInterface>(
670 symbolTable.lookupNearestSymbolFrom(mcmcOp, mcmcOp.getFnAttr()));
671 positionSize =
672 computePositionSizeForSelection(mcmcOp, fn, selection, symbolTable);
673 if (positionSize <= 0)
674 return failure();
675
676 supports = collectSupportInfoForSelection(mcmcOp, fn, selection,
677 allAddresses, symbolTable);
678
679 auto fnType = cast<FunctionType>(fn.getFunctionType());
680 fnResultTypes.assign(fnType.getResults().begin(),
681 fnType.getResults().end());
682 }
683
684 int64_t numSamples = mcmcOp.getNumSamples();
685 int64_t thinning = mcmcOp.getThinning();
686 int64_t numWarmup = mcmcOp.getNumWarmup();
687
688 int64_t totalWarmup = numWarmup;
689 if (auto tw = mcmcOp.getTotalWarmup())
690 totalWarmup = *tw;
691 bool exposeAdaptation = !mcmcOp.getAdaptationStateOut().empty();
692 SmallVector<Value> adaptationInVals(mcmcOp.getAdaptationStateIn().begin(),
693 mcmcOp.getAdaptationStateIn().end());
694 bool hasAdaptationIn = !adaptationInVals.empty();
695
696 auto elemType =
697 cast<RankedTensorType>(stepSize.getType()).getElementType();
698 auto positionType = RankedTensorType::get({1, positionSize}, elemType);
699 auto scalarType = RankedTensorType::get({}, elemType);
700 auto i64TensorType = RankedTensorType::get({}, rewriter.getI64Type());
701 auto i1TensorType = RankedTensorType::get({}, rewriter.getI1Type());
702
703 // Algorithm-specific configuration
704 Value trajectoryLength;
705 Value maxDeltaEnergy;
706 int64_t maxTreeDepth = 0;
707
708 bool adaptStepSize = false;
709 bool adaptMassMatrix = false;
710 auto F64TensorType = RankedTensorType::get({}, rewriter.getF64Type());
711 if (isHMC) {
712 auto hmcConfig = mcmcOp.getHmcConfig().value();
713 double length = hmcConfig.getTrajectoryLength().getValueAsDouble();
714 trajectoryLength = arith::ConstantOp::create(
715 rewriter, loc, F64TensorType,
716 DenseElementsAttr::get(F64TensorType,
717 rewriter.getF64FloatAttr(length)));
718 adaptStepSize = hmcConfig.getAdaptStepSize();
719 adaptMassMatrix = hmcConfig.getAdaptMassMatrix();
720 } else {
721 auto nutsConfig = mcmcOp.getNutsConfig().value();
722 maxTreeDepth = nutsConfig.getMaxTreeDepth();
723 adaptStepSize = nutsConfig.getAdaptStepSize();
724 adaptMassMatrix = nutsConfig.getAdaptMassMatrix();
725 double maxDeltaEnergyVal =
726 nutsConfig.getMaxDeltaEnergy()
727 ? nutsConfig.getMaxDeltaEnergy().getValueAsDouble()
728 : 1000.0;
729 maxDeltaEnergy = arith::ConstantOp::create(
730 rewriter, loc, F64TensorType,
731 DenseElementsAttr::get(
732 F64TensorType, rewriter.getF64FloatAttr(maxDeltaEnergyVal)));
733 }
734
735 bool diagonal = true;
736 if (invMass) {
737 auto invMassType = cast<RankedTensorType>(invMass.getType());
738 diagonal = (invMassType.getRank() == 1);
739 }
740
741 auto adaptedMassMatrixSqrt =
742 computeMassMatrixSqrt(rewriter, loc, adaptedInvMass, positionType);
743
744 auto autodiffAttrs = mcmcOp.getAutodiffAttrsAttr();
745
746 auto makeHMCContext = [&](Value currentInvMass,
747 Value currentMassMatrixSqrt,
748 Value currentStepSize) -> HMCContext {
749 if (hasLogpdfFn) {
750 return HMCContext(logpdfFnAttr, fnInputs, currentInvMass,
751 currentMassMatrixSqrt, currentStepSize,
752 trajectoryLength, positionSize, autodiffAttrs);
753 } else {
754 return HMCContext(mcmcOp.getFnAttr(), fnInputs, fnResultTypes,
755 originalTrace, selection, allAddresses,
756 currentInvMass, currentMassMatrixSqrt,
757 currentStepSize, trajectoryLength, positionSize,
758 supports, autodiffAttrs);
759 }
760 };
761
762 auto makeNUTSContext =
763 [&](Value currentInvMass, Value currentMassMatrixSqrt,
764 Value currentStepSize, Value U) -> NUTSContext {
765 if (hasLogpdfFn) {
766 return NUTSContext(logpdfFnAttr, fnInputs, currentInvMass,
767 currentMassMatrixSqrt, currentStepSize,
768 positionSize, U, maxDeltaEnergy, maxTreeDepth,
769 autodiffAttrs);
770 } else {
771 return NUTSContext(mcmcOp.getFnAttr(), fnInputs, fnResultTypes,
772 originalTrace, selection, allAddresses,
773 currentInvMass, currentMassMatrixSqrt,
774 currentStepSize, positionSize, supports, U,
775 maxDeltaEnergy, maxTreeDepth, autodiffAttrs);
776 }
777 };
778
779 Value currentQ, currentGrad, currentU, currentRng;
780
781 auto initialGrad = mcmcOp.getInitialGradient();
782 auto initialPE = mcmcOp.getInitialPotentialEnergy();
783
784 if (hasLogpdfFn && initialGrad && initialPE) {
785 currentQ = mcmcOp.getInitialPosition();
786 currentGrad = initialGrad;
787 currentU = initialPE;
788 currentRng = rngInput;
789 } else {
790 auto baseCtx =
791 makeHMCContext(adaptedInvMass, adaptedMassMatrixSqrt, stepSize);
792 auto initState = InitHMC(
793 rewriter, loc, rngInput, baseCtx,
794 hasLogpdfFn ? mcmcOp.getInitialPosition() : Value(), debugDump);
795 currentQ = initState.q0;
796 currentGrad = initState.grad0;
797 currentU = initState.U0;
798 currentRng = initState.rng;
799 }
800
801 auto runSampleStepWithStepSize =
802 [&](OpBuilder &builder, Location loc, Value q, Value grad, Value U,
803 Value rng, Value currentStepSize) -> MCMCKernelResult {
804 if (isHMC) {
805 auto ctx = makeHMCContext(adaptedInvMass, adaptedMassMatrixSqrt,
806 currentStepSize);
807 return SampleHMC(builder, loc, q, grad, U, rng, ctx, debugDump);
808 } else {
809 auto nutsCtx = makeNUTSContext(adaptedInvMass, adaptedMassMatrixSqrt,
810 currentStepSize, U);
811 return SampleNUTS(builder, loc, q, grad, U, rng, nutsCtx, debugDump);
812 }
813 };
814
815 Value adaptedStepSize = stepSize;
816
817 auto runSampleStepWithInvMass =
818 [&](OpBuilder &builder, Location loc, Value q, Value grad, Value U,
819 Value rng, Value currentStepSize, Value currentInvMass,
820 Value currentMassMatrixSqrt) -> MCMCKernelResult {
821 if (isHMC) {
822 auto ctx = makeHMCContext(currentInvMass, currentMassMatrixSqrt,
823 currentStepSize);
824 return SampleHMC(builder, loc, q, grad, U, rng, ctx, debugDump);
825 } else {
826 auto nutsCtx = makeNUTSContext(currentInvMass, currentMassMatrixSqrt,
827 currentStepSize, U);
828 return SampleNUTS(builder, loc, q, grad, U, rng, nutsCtx, debugDump);
829 }
830 };
831
832 if (!adaptedInvMass) {
833 adaptedInvMass = arith::ConstantOp::create(
834 rewriter, loc, positionType,
835 DenseElementsAttr::get(positionType,
836 rewriter.getFloatAttr(elemType, 1.0)));
837 adaptedMassMatrixSqrt = arith::ConstantOp::create(
838 rewriter, loc, positionType,
839 DenseElementsAttr::get(positionType,
840 rewriter.getFloatAttr(elemType, 1.0)));
841 }
842
843 SmallVector<Value> finalAdaptationState;
844 auto packAdaptation = [&](const DualAveragingState &da,
845 const WelfordState &w,
846 Value widx) -> SmallVector<Value> {
847 SmallVector<Value> v;
848 for (Value x : da.toValues())
849 v.push_back(x);
850 v.push_back(w.mean);
851 v.push_back(w.m2);
852 v.push_back(w.n);
853 v.push_back(widx);
854 return v;
855 };
856
857 if (numWarmup > 0) {
858 auto c0 = arith::ConstantOp::create(
859 rewriter, loc, i64TensorType,
860 DenseElementsAttr::get(i64TensorType,
861 rewriter.getI64IntegerAttr(0)));
862 auto c1 = arith::ConstantOp::create(
863 rewriter, loc, i64TensorType,
864 DenseElementsAttr::get(i64TensorType,
865 rewriter.getI64IntegerAttr(1)));
866 auto numWarmupConst = arith::ConstantOp::create(
867 rewriter, loc, i64TensorType,
868 DenseElementsAttr::get(i64TensorType,
869 rewriter.getI64IntegerAttr(numWarmup)));
870
871 auto schedule = buildAdaptationSchedule(totalWarmup);
872 int64_t numWindows = static_cast<int64_t>(schedule.size());
873
874 SmallVector<Value> windowEndConstants;
875 for (const auto &window : schedule) {
876 windowEndConstants.push_back(arith::ConstantOp::create(
877 rewriter, loc, i64TensorType,
878 DenseElementsAttr::get(i64TensorType,
879 rewriter.getI64IntegerAttr(window.end))));
880 }
881
882 auto numWindowsMinusOne = arith::ConstantOp::create(
883 rewriter, loc, i64TensorType,
884 DenseElementsAttr::get(i64TensorType,
885 rewriter.getI64IntegerAttr(numWindows - 1)));
886 auto lastIterConst = arith::ConstantOp::create(
887 rewriter, loc, i64TensorType,
888 DenseElementsAttr::get(
889 i64TensorType, rewriter.getI64IntegerAttr(totalWarmup - 1)));
890
891 if (!adaptedInvMass) {
892 adaptedInvMass = arith::ConstantOp::create(
893 rewriter, loc, positionType,
894 DenseElementsAttr::get(positionType,
895 rewriter.getFloatAttr(elemType, 1.0)));
896 adaptedMassMatrixSqrt = arith::ConstantOp::create(
897 rewriter, loc, positionType,
898 DenseElementsAttr::get(positionType,
899 rewriter.getFloatAttr(elemType, 1.0)));
900 }
901
902 Value initialStepSize = stepSize;
903 initialStepSize =
904 conditionalDump(rewriter, loc, initialStepSize,
905 "MCMC: initial step size before warmup", debugDump);
906 DualAveragingState daState =
907 hasAdaptationIn
909 ArrayRef<Value>(adaptationInVals).take_front(5))
910 : initDualAveraging(rewriter, loc, initialStepSize,
911 /*logOfProduct=*/true);
912
913 WelfordState welfordState;
914 WelfordConfig welfordConfig;
915 if (adaptMassMatrix) {
916 welfordState =
917 hasAdaptationIn
919 ArrayRef<Value>(adaptationInVals).slice(5, 3))
920 : initWelford(rewriter, loc, positionSize, diagonal);
921 welfordConfig.diagonal = diagonal;
922 welfordConfig.regularize = true;
923 }
924
925 Value windowIdx =
926 hasAdaptationIn
927 ? adaptationInVals[8]
928 : arith::ConstantOp::create(
929 rewriter, loc, i64TensorType,
930 DenseElementsAttr::get(i64TensorType,
931 rewriter.getI64IntegerAttr(0)));
932
933 // Warmup loop carries by default:
934 // [q, grad, U, rng, stepSize, invMass, massMatrixSqrt, daState(5),
935 // welfordState(3)?, windowIdx]
936 SmallVector<Type> warmupLoopTypes = {positionType,
937 positionType,
938 scalarType,
939 currentRng.getType(),
940 scalarType, // stepSize
941 adaptedInvMass.getType(),
942 adaptedMassMatrixSqrt.getType()};
943 for (Type t : daState.getTypes())
944 warmupLoopTypes.push_back(t);
945 if (adaptMassMatrix) {
946 for (Type t : welfordState.getTypes())
947 warmupLoopTypes.push_back(t);
948 }
949 warmupLoopTypes.push_back(i64TensorType); // windowIdx
950
951 SmallVector<Value> warmupInitArgs = {currentQ,
952 currentGrad,
953 currentU,
954 currentRng,
955 initialStepSize,
956 adaptedInvMass,
957 adaptedMassMatrixSqrt};
958 for (Value v : daState.toValues())
959 warmupInitArgs.push_back(v);
960 if (adaptMassMatrix) {
961 for (Value v : welfordState.toValues())
962 warmupInitArgs.push_back(v);
963 }
964 warmupInitArgs.push_back(windowIdx);
965
966 auto warmupLoop =
967 impulse::ForOp::create(rewriter, loc, warmupLoopTypes, c0,
968 numWarmupConst, c1, warmupInitArgs);
969
970 Block *warmupBody = rewriter.createBlock(&warmupLoop.getRegion());
971 warmupBody->addArgument(i64TensorType, loc); // iteration index t
972 for (Type t : warmupLoopTypes)
973 warmupBody->addArgument(t, loc);
974
975 rewriter.setInsertionPointToStart(warmupBody);
976
977 Value iterT = warmupBody->getArgument(0);
978 Value iterTGlobal = iterT;
979 if (Value warmupOffset = mcmcOp.getWarmupOffset())
980 iterTGlobal =
981 arith::AddIOp::create(rewriter, loc, warmupOffset, iterT);
982 Value qLoop = warmupBody->getArgument(1);
983 Value gradLoop = warmupBody->getArgument(2);
984 Value ULoop = warmupBody->getArgument(3);
985 Value rngLoop = warmupBody->getArgument(4);
986 Value stepSizeLoop = warmupBody->getArgument(5);
987 Value invMassLoop = warmupBody->getArgument(6);
988 Value massMatrixSqrtLoop = warmupBody->getArgument(7);
989
990 SmallVector<Value> daStateLoopValues;
991 for (int i = 0; i < 5; ++i)
992 daStateLoopValues.push_back(warmupBody->getArgument(8 + i));
993 auto daStateLoop = DualAveragingState::fromValues(daStateLoopValues);
994
995 WelfordState welfordStateLoop;
996 Value windowIdxLoop;
997 if (adaptMassMatrix) {
998 SmallVector<Value> welfordStateLoopValues;
999 for (int i = 0; i < 3; ++i)
1000 welfordStateLoopValues.push_back(warmupBody->getArgument(13 + i));
1001 welfordStateLoop = WelfordState::fromValues(welfordStateLoopValues);
1002 windowIdxLoop = warmupBody->getArgument(16);
1003 } else {
1004 windowIdxLoop = warmupBody->getArgument(13);
1005 }
1006
1007 auto sample = runSampleStepWithInvMass(rewriter, loc, qLoop, gradLoop,
1008 ULoop, rngLoop, stepSizeLoop,
1009 invMassLoop, massMatrixSqrtLoop);
1010
1011 // Update dual averaging state
1012 DualAveragingConfig daConfig;
1013 DualAveragingState updatedDaState;
1014 Value currentStepSizeFromDA;
1015 Value finalStepSizeFromDA;
1016
1017 if (adaptStepSize) {
1018 updatedDaState = updateDualAveraging(rewriter, loc, daStateLoop,
1019 sample.accept_prob, daConfig);
1020 currentStepSizeFromDA =
1021 getStepSizeFromDualAveraging(rewriter, loc, updatedDaState);
1022 finalStepSizeFromDA =
1023 getStepSizeFromDualAveraging(rewriter, loc, updatedDaState, true);
1024 } else {
1025 updatedDaState = daStateLoop;
1026 currentStepSizeFromDA = stepSizeLoop;
1027 finalStepSizeFromDA = stepSizeLoop;
1028 }
1029
1030 // Use log_step_size_avg at last iteration
1031 auto isLastIter =
1032 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1033 iterTGlobal, lastIterConst);
1034 Value adaptedStepSizeInLoop = impulse::SelectOp::create(
1035 rewriter, loc, scalarType, isLastIter, finalStepSizeFromDA,
1036 currentStepSizeFromDA);
1037
1038 const auto &floatSemantics =
1039 cast<FloatType>(elemType).getFloatSemantics();
1040 auto tinyConst = arith::ConstantOp::create(
1041 rewriter, loc, scalarType,
1042 DenseElementsAttr::get(
1043 scalarType, FloatAttr::get(elemType, llvm::APFloat::getSmallest(
1044 floatSemantics))));
1045 auto maxConst = arith::ConstantOp::create(
1046 rewriter, loc, scalarType,
1047 DenseElementsAttr::get(
1048 scalarType, FloatAttr::get(elemType, llvm::APFloat::getLargest(
1049 floatSemantics))));
1050 adaptedStepSizeInLoop = arith::MaximumFOp::create(
1051 rewriter, loc, adaptedStepSizeInLoop, tinyConst);
1052 adaptedStepSizeInLoop = arith::MinimumFOp::create(
1053 rewriter, loc, adaptedStepSizeInLoop, maxConst);
1054
1055 auto windowIdxGtZero = arith::CmpIOp::create(
1056 rewriter, loc, arith::CmpIPredicate::sgt, windowIdxLoop, c0);
1057 auto windowIdxLtLast =
1058 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::slt,
1059 windowIdxLoop, numWindowsMinusOne);
1060 auto isMiddleWindow = arith::AndIOp::create(
1061 rewriter, loc, windowIdxGtZero, windowIdxLtLast);
1062
1063 // Conditionally update Welford
1064 WelfordState conditionalWelford;
1065 if (adaptMassMatrix) {
1066 auto sampleType1D = RankedTensorType::get({positionSize}, elemType);
1067 Value sample1D =
1068 impulse::ReshapeOp::create(rewriter, loc, sampleType1D, sample.q);
1069 WelfordState updatedWelfordAfterSample = updateWelford(
1070 rewriter, loc, welfordStateLoop, sample1D, welfordConfig);
1071
1072 conditionalWelford.mean = impulse::SelectOp::create(
1073 rewriter, loc, welfordStateLoop.mean.getType(), isMiddleWindow,
1074 updatedWelfordAfterSample.mean, welfordStateLoop.mean);
1075 conditionalWelford.m2 = impulse::SelectOp::create(
1076 rewriter, loc, welfordStateLoop.m2.getType(), isMiddleWindow,
1077 updatedWelfordAfterSample.m2, welfordStateLoop.m2);
1078 conditionalWelford.n = impulse::SelectOp::create(
1079 rewriter, loc, welfordStateLoop.n.getType(), isMiddleWindow,
1080 updatedWelfordAfterSample.n, welfordStateLoop.n);
1081 }
1082
1083 Value atWindowEnd = arith::ConstantOp::create(
1084 rewriter, loc, i1TensorType,
1085 DenseElementsAttr::get(i1TensorType, rewriter.getBoolAttr(false)));
1086
1087 for (int64_t w = 0; w < numWindows; ++w) {
1088 auto windowIdxIsW = arith::CmpIOp::create(
1089 rewriter, loc, arith::CmpIPredicate::eq, windowIdxLoop,
1090 arith::ConstantOp::create(
1091 rewriter, loc, i64TensorType,
1092 DenseElementsAttr::get(i64TensorType,
1093 rewriter.getI64IntegerAttr(w))));
1094 auto tEqualsWindowEnd =
1095 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1096 iterTGlobal, windowEndConstants[w]);
1097 auto matchesThisWindow = arith::AndIOp::create(
1098 rewriter, loc, windowIdxIsW, tEqualsWindowEnd);
1099 atWindowEnd = arith::OrIOp::create(rewriter, loc, atWindowEnd,
1100 matchesThisWindow);
1101 }
1102
1103 Value newWindowIdx =
1104 arith::AddIOp::create(rewriter, loc, windowIdxLoop, c1);
1105 Value windowIdxAfterIncrement =
1106 impulse::SelectOp::create(rewriter, loc, i64TensorType, atWindowEnd,
1107 newWindowIdx, windowIdxLoop);
1108
1109 auto atMiddleWindowEnd =
1110 arith::AndIOp::create(rewriter, loc, atWindowEnd, isMiddleWindow);
1111
1112 Value finalInvMass;
1113 Value finalMassMatrixSqrt;
1114 WelfordState finalWelfordState;
1115 Value finalStepSizeValue;
1116 DualAveragingState finalDaState;
1117
1118 SmallVector<Type> ifResultTypes;
1119 ifResultTypes.push_back(invMassLoop.getType());
1120 ifResultTypes.push_back(massMatrixSqrtLoop.getType());
1121 if (adaptMassMatrix) {
1122 ifResultTypes.push_back(conditionalWelford.mean.getType());
1123 ifResultTypes.push_back(conditionalWelford.m2.getType());
1124 ifResultTypes.push_back(conditionalWelford.n.getType());
1125 }
1126 for (Type t : updatedDaState.getTypes())
1127 ifResultTypes.push_back(t);
1128
1129 auto ifOp = impulse::IfOp::create(rewriter, loc, ifResultTypes,
1130 atMiddleWindowEnd);
1131
1132 {
1133 Block *trueBranch = rewriter.createBlock(&ifOp.getTrueBranch());
1134 rewriter.setInsertionPointToStart(trueBranch);
1135
1136 SmallVector<Value> trueYieldValues;
1137
1138 if (adaptMassMatrix) {
1139 auto newInvMass = finalizeWelford(rewriter, loc, conditionalWelford,
1140 welfordConfig);
1141 auto newMassMatrixSqrt =
1142 computeMassMatrixSqrt(rewriter, loc, newInvMass, positionType);
1143 auto reinitWelford =
1144 initWelford(rewriter, loc, positionSize, diagonal);
1145
1146 trueYieldValues.push_back(newInvMass);
1147 trueYieldValues.push_back(newMassMatrixSqrt);
1148 trueYieldValues.push_back(reinitWelford.mean);
1149 trueYieldValues.push_back(reinitWelford.m2);
1150 trueYieldValues.push_back(reinitWelford.n);
1151 } else {
1152 trueYieldValues.push_back(invMassLoop);
1153 trueYieldValues.push_back(massMatrixSqrtLoop);
1154 }
1155
1156 if (adaptStepSize) {
1157 auto reinitDaState =
1158 initDualAveraging(rewriter, loc, adaptedStepSizeInLoop);
1159 for (auto v : reinitDaState.toValues())
1160 trueYieldValues.push_back(v);
1161 } else {
1162 for (auto v : updatedDaState.toValues())
1163 trueYieldValues.push_back(v);
1164 }
1165
1166 impulse::YieldOp::create(rewriter, loc, trueYieldValues);
1167 }
1168
1169 {
1170 Block *falseBranch = rewriter.createBlock(&ifOp.getFalseBranch());
1171 rewriter.setInsertionPointToStart(falseBranch);
1172
1173 SmallVector<Value> falseYieldValues;
1174 falseYieldValues.push_back(invMassLoop);
1175 falseYieldValues.push_back(massMatrixSqrtLoop);
1176 if (adaptMassMatrix) {
1177 falseYieldValues.push_back(conditionalWelford.mean);
1178 falseYieldValues.push_back(conditionalWelford.m2);
1179 falseYieldValues.push_back(conditionalWelford.n);
1180 }
1181 for (auto v : updatedDaState.toValues())
1182 falseYieldValues.push_back(v);
1183
1184 impulse::YieldOp::create(rewriter, loc, falseYieldValues);
1185 }
1186
1187 rewriter.setInsertionPointAfter(ifOp);
1188
1189 size_t resultIdx = 0;
1190 finalInvMass = ifOp.getResult(resultIdx++);
1191 finalMassMatrixSqrt = ifOp.getResult(resultIdx++);
1192 if (adaptMassMatrix) {
1193 finalWelfordState.mean = ifOp.getResult(resultIdx++);
1194 finalWelfordState.m2 = ifOp.getResult(resultIdx++);
1195 finalWelfordState.n = ifOp.getResult(resultIdx++);
1196 }
1197 finalDaState.log_step_size = ifOp.getResult(resultIdx++);
1198 finalDaState.log_step_size_avg = ifOp.getResult(resultIdx++);
1199 finalDaState.gradient_avg = ifOp.getResult(resultIdx++);
1200 finalDaState.step_count = ifOp.getResult(resultIdx++);
1201 finalDaState.prox_center = ifOp.getResult(resultIdx++);
1202
1203 finalStepSizeValue = adaptedStepSizeInLoop;
1204
1205 SmallVector<Value> warmupYieldValues = {
1206 sample.q, sample.grad, sample.U, sample.rng,
1207 finalStepSizeValue, finalInvMass, finalMassMatrixSqrt};
1208 for (Value v : finalDaState.toValues())
1209 warmupYieldValues.push_back(v);
1210 if (adaptMassMatrix) {
1211 for (Value v : finalWelfordState.toValues())
1212 warmupYieldValues.push_back(v);
1213 }
1214 warmupYieldValues.push_back(windowIdxAfterIncrement);
1215
1216 impulse::YieldOp::create(rewriter, loc, warmupYieldValues);
1217
1218 rewriter.setInsertionPointAfter(warmupLoop);
1219
1220 currentQ = warmupLoop.getResult(0);
1221 currentGrad = warmupLoop.getResult(1);
1222 currentU = warmupLoop.getResult(2);
1223 currentRng = warmupLoop.getResult(3);
1224 adaptedStepSize = warmupLoop.getResult(4);
1225 adaptedInvMass = warmupLoop.getResult(5);
1226 adaptedMassMatrixSqrt = warmupLoop.getResult(6);
1227
1228 adaptedStepSize =
1229 conditionalDump(rewriter, loc, adaptedStepSize,
1230 "MCMC: adapted step size after warmup", debugDump);
1231 if (adaptMassMatrix) {
1232 adaptedInvMass = conditionalDump(
1233 rewriter, loc, adaptedInvMass,
1234 "MCMC: adapted inverse mass matrix after warmup", debugDump);
1235 }
1236
1237 if (exposeAdaptation) {
1239 {warmupLoop.getResult(7), warmupLoop.getResult(8),
1240 warmupLoop.getResult(9), warmupLoop.getResult(10),
1241 warmupLoop.getResult(11)});
1242 WelfordState finalW;
1243 Value finalWidx;
1244 if (adaptMassMatrix) {
1245 finalW = WelfordState::fromValues({warmupLoop.getResult(12),
1246 warmupLoop.getResult(13),
1247 warmupLoop.getResult(14)});
1248 finalWidx = warmupLoop.getResult(15);
1249 } else {
1250 finalW = initWelford(rewriter, loc, positionSize, diagonal);
1251 finalWidx = warmupLoop.getResult(12);
1252 }
1253 finalAdaptationState = packAdaptation(finalDa, finalW, finalWidx);
1254 }
1255 }
1256
1257 if (exposeAdaptation && finalAdaptationState.empty()) {
1259 hasAdaptationIn
1261 ArrayRef<Value>(adaptationInVals).take_front(5))
1262 : initDualAveraging(rewriter, loc, stepSize,
1263 /*logOfProduct=*/true);
1264 WelfordState w =
1265 hasAdaptationIn
1267 ArrayRef<Value>(adaptationInVals).slice(5, 3))
1268 : initWelford(rewriter, loc, positionSize, diagonal);
1269 Value widx =
1270 hasAdaptationIn
1271 ? adaptationInVals[8]
1272 : arith::ConstantOp::create(
1273 rewriter, loc, i64TensorType,
1274 DenseElementsAttr::get(i64TensorType,
1275 rewriter.getI64IntegerAttr(0)));
1276 finalAdaptationState = packAdaptation(da, w, widx);
1277 }
1278
1279 int64_t collectionSize = numSamples / thinning;
1280 int64_t startIdx = numSamples % thinning;
1281
1282 auto samplesBufferType =
1283 RankedTensorType::get({collectionSize, positionSize}, elemType);
1284 auto diagnosticsBufferType =
1285 // Diagnostics buffer has two columns: [is_accepted, is_divergent]
1286 RankedTensorType::get({collectionSize, 2}, rewriter.getI1Type());
1287 auto logDensityBufferType =
1288 RankedTensorType::get({collectionSize}, elemType);
1289
1290 auto samplesBuffer = arith::ConstantOp::create(
1291 rewriter, loc, samplesBufferType,
1292 DenseElementsAttr::get(samplesBufferType,
1293 rewriter.getFloatAttr(elemType, 0.0)));
1294 auto diagnosticsBuffer = arith::ConstantOp::create(
1295 rewriter, loc, diagnosticsBufferType,
1296 DenseElementsAttr::get(diagnosticsBufferType,
1297 rewriter.getBoolAttr(isNUTS)));
1298 auto logDensityBuffer = arith::ConstantOp::create(
1299 rewriter, loc, logDensityBufferType,
1300 DenseElementsAttr::get(logDensityBufferType,
1301 rewriter.getFloatAttr(elemType, 0.0)));
1302
1303 auto c0 = arith::ConstantOp::create(
1304 rewriter, loc, i64TensorType,
1305 DenseElementsAttr::get(i64TensorType, rewriter.getI64IntegerAttr(0)));
1306 auto c1 = arith::ConstantOp::create(
1307 rewriter, loc, i64TensorType,
1308 DenseElementsAttr::get(i64TensorType, rewriter.getI64IntegerAttr(1)));
1309 auto numSamplesConst = arith::ConstantOp::create(
1310 rewriter, loc, i64TensorType,
1311 DenseElementsAttr::get(i64TensorType,
1312 rewriter.getI64IntegerAttr(numSamples)));
1313 auto startIdxConst = arith::ConstantOp::create(
1314 rewriter, loc, i64TensorType,
1315 DenseElementsAttr::get(i64TensorType,
1316 rewriter.getI64IntegerAttr(startIdx)));
1317 auto thinningConst = arith::ConstantOp::create(
1318 rewriter, loc, i64TensorType,
1319 DenseElementsAttr::get(i64TensorType,
1320 rewriter.getI64IntegerAttr(thinning)));
1321
1322 Value finalQ, finalGrad, finalU, finalRng, finalSamplesBuffer,
1323 finalDiagnosticsBuffer, finalLogDensityBuffer;
1324 if (collectionSize == 0) {
1325 finalQ = currentQ;
1326 finalGrad = currentGrad;
1327 finalU = currentU;
1328 finalRng = currentRng;
1329 finalSamplesBuffer = samplesBuffer;
1330 finalDiagnosticsBuffer = diagnosticsBuffer;
1331 finalLogDensityBuffer = logDensityBuffer;
1332 } else {
1333 // Loop carries: [q, grad, U, rng, samplesBuffer, diagnosticsBuffer,
1334 // logDensityBuffer]
1335 SmallVector<Type> loopResultTypes = {
1336 positionType, positionType, scalarType,
1337 currentRng.getType(), samplesBufferType, diagnosticsBufferType,
1338 logDensityBufferType};
1339 auto forLoopOp = impulse::ForOp::create(
1340 rewriter, loc, loopResultTypes, c0, numSamplesConst, c1,
1341 ValueRange{currentQ, currentGrad, currentU, currentRng,
1342 samplesBuffer, diagnosticsBuffer, logDensityBuffer});
1343
1344 Block *loopBody = rewriter.createBlock(&forLoopOp.getRegion());
1345 loopBody->addArgument(i64TensorType, loc); // i (iteration index)
1346 loopBody->addArgument(positionType, loc); // q
1347 loopBody->addArgument(positionType, loc); // grad
1348 loopBody->addArgument(scalarType, loc); // U
1349 loopBody->addArgument(currentRng.getType(), loc); // rng
1350 loopBody->addArgument(samplesBufferType, loc); // samplesBuffer
1351 loopBody->addArgument(diagnosticsBufferType, loc); // diagnosticsBuffer
1352 loopBody->addArgument(logDensityBufferType, loc); // logDensityBuffer
1353
1354 rewriter.setInsertionPointToStart(loopBody);
1355 Value iterIdx = loopBody->getArgument(0);
1356 Value qLoop = loopBody->getArgument(1);
1357 Value gradLoop = loopBody->getArgument(2);
1358 Value ULoop = loopBody->getArgument(3);
1359 Value rngLoop = loopBody->getArgument(4);
1360 Value samplesBufferLoop = loopBody->getArgument(5);
1361 Value diagnosticsBufferLoop = loopBody->getArgument(6);
1362 Value logDensityBufferLoop = loopBody->getArgument(7);
1363
1364 auto sample = runSampleStepWithStepSize(
1365 rewriter, loc, qLoop, gradLoop, ULoop, rngLoop, adaptedStepSize);
1366 auto q_constrained =
1367 impulse::constrainPosition(rewriter, loc, sample.q, supports);
1368
1369 // Storage index: idx = (i - start_idx) / thinning
1370 auto iMinusStart =
1371 arith::SubIOp::create(rewriter, loc, iterIdx, startIdxConst);
1372 auto storageIdx =
1373 arith::DivSIOp::create(rewriter, loc, iMinusStart, thinningConst);
1374
1375 // Store condition:
1376 // (i >= start_idx) && ((i - start_idx) % thinning == 0)
1377 auto geStartIdx = arith::CmpIOp::create(
1378 rewriter, loc, arith::CmpIPredicate::sge, iterIdx, startIdxConst);
1379 auto modThinning =
1380 arith::RemSIOp::create(rewriter, loc, iMinusStart, thinningConst);
1381 auto modIsZero = arith::CmpIOp::create(
1382 rewriter, loc, arith::CmpIPredicate::eq, modThinning, c0);
1383 auto shouldStore =
1384 arith::AndIOp::create(rewriter, loc, geStartIdx, modIsZero);
1385
1386 auto zeroCol = arith::ConstantOp::create(
1387 rewriter, loc, i64TensorType,
1388 DenseElementsAttr::get(i64TensorType,
1389 rewriter.getI64IntegerAttr(0)));
1390 auto updatedSamplesBuffer = impulse::DynamicUpdateSliceOp::create(
1391 rewriter, loc, samplesBufferType, samplesBufferLoop, q_constrained,
1392 ValueRange{storageIdx, zeroCol});
1393 auto selectedSamplesBuffer = impulse::SelectOp::create(
1394 rewriter, loc, samplesBufferType, shouldStore, updatedSamplesBuffer,
1395 samplesBufferLoop);
1396
1397 auto oneCol = arith::ConstantOp::create(
1398 rewriter, loc, i64TensorType,
1399 DenseElementsAttr::get(i64TensorType,
1400 rewriter.getI64IntegerAttr(1)));
1401 auto accepted1x1 = impulse::ReshapeOp::create(
1402 rewriter, loc, RankedTensorType::get({1, 1}, rewriter.getI1Type()),
1403 sample.accepted);
1404 auto divergent1x1 = impulse::ReshapeOp::create(
1405 rewriter, loc, RankedTensorType::get({1, 1}, rewriter.getI1Type()),
1406 sample.divergent);
1407 auto updatedDiagnosticsBuffer = impulse::DynamicUpdateSliceOp::create(
1408 rewriter, loc, diagnosticsBufferType, diagnosticsBufferLoop,
1409 accepted1x1, ValueRange{storageIdx, zeroCol});
1410 updatedDiagnosticsBuffer = impulse::DynamicUpdateSliceOp::create(
1411 rewriter, loc, diagnosticsBufferType, updatedDiagnosticsBuffer,
1412 divergent1x1, ValueRange{storageIdx, oneCol});
1413 auto selectedDiagnosticsBuffer = impulse::SelectOp::create(
1414 rewriter, loc, diagnosticsBufferType, shouldStore,
1415 updatedDiagnosticsBuffer, diagnosticsBufferLoop);
1416
1417 auto negU = arith::NegFOp::create(rewriter, loc, sample.U);
1418 auto logDensity1D = impulse::ReshapeOp::create(
1419 rewriter, loc, RankedTensorType::get({1}, elemType), negU);
1420 auto updatedLogDensityBuffer = impulse::DynamicUpdateSliceOp::create(
1421 rewriter, loc, logDensityBufferType, logDensityBufferLoop,
1422 logDensity1D, ValueRange{storageIdx});
1423 auto selectedLogDensityBuffer = impulse::SelectOp::create(
1424 rewriter, loc, logDensityBufferType, shouldStore,
1425 updatedLogDensityBuffer, logDensityBufferLoop);
1426
1427 impulse::YieldOp::create(rewriter, loc,
1428 ValueRange{sample.q, sample.grad, sample.U,
1429 sample.rng, selectedSamplesBuffer,
1430 selectedDiagnosticsBuffer,
1431 selectedLogDensityBuffer});
1432
1433 rewriter.setInsertionPointAfter(forLoopOp);
1434 finalQ = forLoopOp.getResult(0);
1435 finalGrad = forLoopOp.getResult(1);
1436 finalU = forLoopOp.getResult(2);
1437 finalRng = forLoopOp.getResult(3);
1438 finalSamplesBuffer = forLoopOp.getResult(4);
1439 finalDiagnosticsBuffer = forLoopOp.getResult(5);
1440 finalLogDensityBuffer = forLoopOp.getResult(6);
1441 }
1442
1443 finalSamplesBuffer =
1444 conditionalDump(rewriter, loc, finalSamplesBuffer,
1445 "MCMC: collected samples", debugDump);
1446
1447 SmallVector<Value> replacements = {finalSamplesBuffer,
1448 finalDiagnosticsBuffer,
1449 finalLogDensityBuffer,
1450 finalRng,
1451 finalQ,
1452 finalGrad,
1453 finalU,
1454 adaptedStepSize,
1455 adaptedInvMass};
1456 if (exposeAdaptation)
1457 replacements.append(finalAdaptationState.begin(),
1458 finalAdaptationState.end());
1459 rewriter.replaceOp(mcmcOp, replacements);
1460
1461 return success();
1462 }
1463 };
1464
1465 struct LowerMHPattern : public mlir::OpRewritePattern<impulse::MHOp> {
1466 using mlir::OpRewritePattern<impulse::MHOp>::OpRewritePattern;
1467
1468 LogicalResult matchAndRewrite(impulse::MHOp mhOp,
1469 PatternRewriter &rewriter) const override {
1470 SymbolTableCollection symbolTable;
1471
1472 auto fn = cast<FunctionOpInterface>(
1473 symbolTable.lookupNearestSymbolFrom(mhOp, mhOp.getFnAttr()));
1474
1475 if (fn.getFunctionBody().empty()) {
1476 mhOp.emitError(
1477 "Impulse: calling `mh` on an empty function; if this is a "
1478 "distribution function, its sample op should have a logpdf "
1479 "attribute to avoid recursive `mh` calls which is intended for "
1480 "generative functions");
1481 return failure();
1482 }
1483
1484 auto loc = mhOp.getLoc();
1485
1486 Value oldTrace = mhOp.getOperand(0);
1487 Value oldWeight = mhOp.getOperand(1);
1488 SmallVector<Value> inputs;
1489 for (unsigned i = 2; i < mhOp.getNumOperands(); ++i)
1490 inputs.push_back(mhOp.getOperand(i));
1491 auto selection = mhOp.getSelectionAttr();
1492
1493 auto traceType = oldTrace.getType();
1494 auto weightType = cast<RankedTensorType>(oldWeight.getType());
1495 auto rngStateType = inputs[0].getType();
1496
1497 // 1. Create regenerate op with the same function and selection
1498 auto nameAttr = mhOp.getNameAttr();
1499 if (!nameAttr)
1500 nameAttr = rewriter.getStringAttr("");
1501
1502 auto regenerateAddresses = mhOp.getRegenerateAddressesAttr();
1503
1504 SmallVector<Type> regenResultTypes;
1505 regenResultTypes.push_back(traceType);
1506 regenResultTypes.push_back(weightType);
1507 for (auto t : fn.getResultTypes())
1508 regenResultTypes.push_back(t);
1509
1510 auto regenerateOp = rewriter.create<impulse::RegenerateOp>(
1511 loc,
1512 /*resultTypes*/ regenResultTypes,
1513 /*fn*/ mhOp.getFnAttr(),
1514 /*inputs*/ inputs,
1515 /*original_trace*/ oldTrace,
1516 /*selection*/ selection,
1517 /*regenerate_addresses*/ regenerateAddresses,
1518 /*name*/ nameAttr);
1519
1520 Value newTrace = regenerateOp.getNewTrace();
1521 Value newWeight = regenerateOp.getWeight();
1522 Value newRng = regenerateOp.getOutputs()[0];
1523
1524 // 2. Compute log_alpha = new_weight - old_weight
1525 auto logAlpha =
1526 arith::SubFOp::create(rewriter, loc, newWeight, oldWeight);
1527
1528 // 3. Sample uniform random in (0, 1) and compute log
1529 auto zeroConst = arith::ConstantOp::create(
1530 rewriter, loc, weightType, DenseElementsAttr::get(weightType, 0.0));
1531 auto oneConst = arith::ConstantOp::create(
1532 rewriter, loc, weightType, DenseElementsAttr::get(weightType, 1.0));
1533
1534 auto randomOp = impulse::RandomOp::create(
1535 rewriter, loc, TypeRange{rngStateType, weightType}, newRng, zeroConst,
1536 oneConst,
1537 impulse::RngDistributionAttr::get(rewriter.getContext(),
1538 impulse::RngDistribution::UNIFORM));
1539 auto logRand = math::LogOp::create(rewriter, loc, randomOp.getResult());
1540 Value finalRng = randomOp.getOutputRngState();
1541
1542 // 4. Check if proposal is accepted: log(rand()) < log_alpha
1543 auto accepted = arith::CmpFOp::create(
1544 rewriter, loc, arith::CmpFPredicate::OLT, logRand, logAlpha);
1545
1546 // 5. Select trace and weight based on acceptance
1547 auto selectedTrace = impulse::SelectOp::create(
1548 rewriter, loc, traceType, accepted, newTrace, oldTrace);
1549 auto selectedWeight = arith::SelectOp::create(rewriter, loc, accepted,
1550 newWeight, oldWeight);
1551
1552 rewriter.replaceOp(mhOp,
1553 {selectedTrace, selectedWeight, accepted, finalRng});
1554 return success();
1555 }
1556 };
1557
1558 struct LowerGeneratePattern
1559 : public mlir::OpRewritePattern<impulse::GenerateOp> {
1560 using mlir::OpRewritePattern<impulse::GenerateOp>::OpRewritePattern;
1561
1562 LogicalResult matchAndRewrite(impulse::GenerateOp CI,
1563 PatternRewriter &rewriter) const override {
1564 SymbolTableCollection symbolTable;
1565
1566 auto fn = cast<FunctionOpInterface>(
1567 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
1568
1569 if (fn.getFunctionBody().empty()) {
1570 CI.emitError(
1571 "Impulse: calling `generate` on an empty function; if this "
1572 "is a distribution function, its sample op should have a "
1573 "logpdf attribute to avoid recursive `generate` calls which is "
1574 "intended for generative functions");
1575 return failure();
1576 }
1577
1578 ArrayAttr selection = CI.getSelectionAttr();
1579 int64_t positionSize =
1580 computePositionSizeForSelection(CI, fn, selection, symbolTable);
1581 if (positionSize <= 0) {
1582 CI.emitError("Impulse: failed to compute position size for generate");
1583 return failure();
1584 }
1585
1586 int64_t constraintSize = computePositionSizeForSelection(
1587 CI, fn, CI.getConstrainedAddressesAttr(), symbolTable);
1588 if (constraintSize < 0) {
1589 CI.emitError("Impulse: failed to compute constraint size for generate");
1590 return failure();
1591 }
1592
1593 auto putils = ImpulseUtils::CreateFromClone(fn, ImpulseMode::Generate,
1594 positionSize, constraintSize);
1595 FunctionOpInterface NewF = putils->newFunc;
1596
1597 OpBuilder entryBuilder(putils->initializationBlock,
1598 putils->initializationBlock->begin());
1599 Location initLoc = putils->initializationBlock->begin()->getLoc();
1600
1601 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
1602 auto zeroWeight =
1603 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
1604 DenseElementsAttr::get(scalarType, 0.0));
1605 Value weightAccumulator = zeroWeight;
1606
1607 auto traceType =
1608 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
1609 auto zeroTrace =
1610 arith::ConstantOp::create(entryBuilder, initLoc, traceType,
1611 DenseElementsAttr::get(traceType, 0.0));
1612 Value currTrace = zeroTrace;
1613 Value constraint = NewF.getArgument(0);
1614 int64_t currentTraceOffset = 0;
1615
1616 SmallVector<Operation *> toErase;
1617 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
1618 OpBuilder::InsertionGuard guard(rewriter);
1619 rewriter.setInsertionPoint(sampleOp);
1620
1621 SmallVector<Value> sampledValues;
1622 bool isDistribution = static_cast<bool>(sampleOp.getLogpdfAttr());
1623
1624 if (isDistribution) {
1625 // A1. Distribution function: call the distribution function.
1626 bool isConstrained = false;
1627 int64_t constrainedOffset = -1;
1628 for (auto addr : CI.getConstrainedAddressesAttr()) {
1629 auto address = cast<ArrayAttr>(addr);
1630 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
1631 if (address.size() != 1) {
1632 sampleOp.emitError(
1633 "Impulse: distribution function cannot have composite "
1634 "constrained address");
1635 return WalkResult::interrupt();
1636 }
1637 isConstrained = true;
1638 constrainedOffset = computeOffsetForSampleInSelection(
1639 CI, fn, CI.getConstrainedAddressesAttr(),
1640 sampleOp.getSymbolAttr(), symbolTable);
1641 break;
1642 }
1643 }
1644
1645 if (isConstrained) {
1646 // Extract sampled values from constraint tensor
1647 sampledValues.resize(sampleOp.getNumResults());
1648 sampledValues[0] = sampleOp.getOperand(0); // RNG state
1649
1650 for (unsigned i = 1; i < sampleOp.getNumResults(); ++i) {
1651 auto resultType =
1652 cast<RankedTensorType>(sampleOp.getResult(i).getType());
1653 int64_t numElements = computeTensorElementCount(resultType);
1654 if (numElements < 0) {
1655 sampleOp.emitError(
1656 "Impulse: dynamic tensor dimensions not supported");
1657 return WalkResult::interrupt();
1658 }
1659
1660 auto sliceType = RankedTensorType::get(
1661 {1, numElements}, resultType.getElementType());
1662 auto sliced = impulse::SliceOp::create(
1663 rewriter, sampleOp.getLoc(), sliceType, constraint,
1664 rewriter.getDenseI64ArrayAttr({0, constrainedOffset}),
1665 rewriter.getDenseI64ArrayAttr(
1666 {1, constrainedOffset + numElements}),
1667 rewriter.getDenseI64ArrayAttr({1, 1}));
1668 auto extracted = impulse::ReshapeOp::create(
1669 rewriter, sampleOp.getLoc(), resultType, sliced);
1670 sampledValues[i] = extracted.getResult();
1671 constrainedOffset += numElements;
1672 }
1673
1674 // Compute weight via logpdf using constrained values.
1675 auto logpdfFn =
1676 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1677 sampleOp, sampleOp.getLogpdfAttr()));
1678
1679 // logpdf operands: (<non-RNG outputs>..., <non-RNG inputs>...)
1680 SmallVector<Value> logpdfOperands;
1681 for (unsigned i = 1; i < sampledValues.size(); ++i) {
1682 logpdfOperands.push_back(sampledValues[i]);
1683 }
1684 for (unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
1685 logpdfOperands.push_back(sampleOp.getOperand(i));
1686 }
1687
1688 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
1689 sampleOp.emitError(
1690 "Impulse: failed to construct logpdf call for constrained "
1691 "sample; logpdf function has wrong number of arguments");
1692 return WalkResult::interrupt();
1693 }
1694
1695 auto logpdf = func::CallOp::create(
1696 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
1697 logpdfFn.getResultTypes(), logpdfOperands);
1698 weightAccumulator =
1699 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
1700 weightAccumulator, logpdf.getResult(0));
1701 } else {
1702 // Unconstrained: call the distribution function
1703 auto distFn =
1704 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1705 sampleOp, sampleOp.getFnAttr()));
1706
1707 auto distCall = func::CallOp::create(
1708 rewriter, sampleOp.getLoc(), distFn.getName(),
1709 distFn.getResultTypes(), sampleOp.getInputs());
1710
1711 sampledValues.append(distCall.getResults().begin(),
1712 distCall.getResults().end());
1713
1714 auto logpdfFn =
1715 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1716 sampleOp, sampleOp.getLogpdfAttr()));
1717
1718 SmallVector<Value> logpdfOperands;
1719 for (unsigned i = 1; i < sampledValues.size(); ++i) {
1720 logpdfOperands.push_back(sampledValues[i]);
1721 }
1722 for (unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
1723 logpdfOperands.push_back(sampleOp.getOperand(i));
1724 }
1725
1726 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
1727 sampleOp.emitError(
1728 "Impulse: failed to construct logpdf call; "
1729 "logpdf function has wrong number of arguments");
1730 return WalkResult::interrupt();
1731 }
1732
1733 auto logpdf = func::CallOp::create(
1734 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
1735 logpdfFn.getResultTypes(), logpdfOperands);
1736 weightAccumulator =
1737 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
1738 weightAccumulator, logpdf.getResult(0));
1739 }
1740
1741 bool inSelection = false;
1742 for (auto addr : selection) {
1743 auto address = cast<ArrayAttr>(addr);
1744 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
1745 inSelection = true;
1746 break;
1747 }
1748 }
1749
1750 if (inSelection) {
1751 for (unsigned i = 1; i < sampledValues.size(); ++i) {
1752 auto sampleValue = sampledValues[i];
1753 auto sampleType = cast<RankedTensorType>(sampleValue.getType());
1754 int64_t numElements = computeTensorElementCount(sampleType);
1755 if (numElements < 0) {
1756 sampleOp.emitError(
1757 "Impulse: dynamic tensor dimensions not supported");
1758 return WalkResult::interrupt();
1759 }
1760
1761 auto flatSampleType = RankedTensorType::get(
1762 {1, numElements}, sampleType.getElementType());
1763 auto flatSample = impulse::ReshapeOp::create(
1764 rewriter, sampleOp.getLoc(), flatSampleType, sampleValue);
1765 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
1766 auto row0 = arith::ConstantOp::create(
1767 rewriter, sampleOp.getLoc(), i64S,
1768 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
1769 auto colOff = arith::ConstantOp::create(
1770 rewriter, sampleOp.getLoc(), i64S,
1771 DenseElementsAttr::get(
1772 i64S, rewriter.getI64IntegerAttr(currentTraceOffset)));
1773 currTrace = impulse::DynamicUpdateSliceOp::create(
1774 rewriter, sampleOp.getLoc(), traceType, currTrace,
1775 flatSample, ValueRange{row0, colOff})
1776 .getResult();
1777 currentTraceOffset += numElements;
1778 }
1779 }
1780 } else {
1781 // B. Generative function: recursively generate the nested function
1782 auto genFn =
1783 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1784 sampleOp, sampleOp.getFnAttr()));
1785
1786 if (genFn.getFunctionBody().empty()) {
1787 sampleOp.emitError(
1788 "Impulse: generative function body is empty; "
1789 "if this is a distribution, add a logpdf attribute");
1790 return WalkResult::interrupt();
1791 }
1792
1793 ArrayAttr subSelection =
1794 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
1795 ArrayAttr subConstrainedAddrs =
1796 buildSubSelection(rewriter, CI.getConstrainedAddressesAttr(),
1797 sampleOp.getSymbolAttr());
1798
1799 if (subSelection.empty()) {
1800 // No samples from this generative function are in the selection
1801 // Just call the function directly
1802 auto genCall = func::CallOp::create(
1803 rewriter, sampleOp.getLoc(), genFn.getName(),
1804 genFn.getResultTypes(), sampleOp.getInputs());
1805 sampledValues.append(genCall.getResults().begin(),
1806 genCall.getResults().end());
1807 } else {
1808 int64_t subPositionSize = computePositionSizeForSelection(
1809 sampleOp, genFn, subSelection, symbolTable);
1810 int64_t subConstraintSize = computePositionSizeForSelection(
1811 sampleOp, genFn, subConstrainedAddrs, symbolTable);
1812 if (subPositionSize <= 0 || subConstraintSize < 0) {
1813 sampleOp.emitError("Impulse: failed to compute sub-position or "
1814 "sub-constraint size");
1815 return WalkResult::interrupt();
1816 }
1817
1818 Value subConstraint;
1819 auto subConstraintType = RankedTensorType::get(
1820 {1, subConstraintSize}, rewriter.getF64Type());
1821
1822 if (subConstraintSize > 0) {
1823 int64_t subConstraintOffset = computeOffsetForNestedSample(
1824 sampleOp, fn, CI.getConstrainedAddressesAttr(),
1825 sampleOp.getSymbolAttr(), symbolTable);
1826
1827 subConstraint = impulse::SliceOp::create(
1828 rewriter, sampleOp.getLoc(), subConstraintType, constraint,
1829 rewriter.getDenseI64ArrayAttr({0, subConstraintOffset}),
1830 rewriter.getDenseI64ArrayAttr(
1831 {1, subConstraintOffset + subConstraintSize}),
1832 rewriter.getDenseI64ArrayAttr({1, 1}));
1833 } else {
1834 subConstraint = arith::ConstantOp::create(
1835 rewriter, sampleOp.getLoc(), subConstraintType,
1836 DenseElementsAttr::get(subConstraintType, {0.0}));
1837 }
1838
1839 // Build result types: (trace, weight, original_returns...)
1840 auto subTraceType = RankedTensorType::get({1, subPositionSize},
1841 rewriter.getF64Type());
1842 auto scalarTy = RankedTensorType::get({}, rewriter.getF64Type());
1843 SmallVector<Type> genResultTypes;
1844 genResultTypes.push_back(subTraceType);
1845 genResultTypes.push_back(scalarTy);
1846 for (auto t : genFn.getResultTypes())
1847 genResultTypes.push_back(t);
1848
1849 auto nestedGenerate = impulse::GenerateOp::create(
1850 rewriter, sampleOp.getLoc(), genResultTypes,
1851 sampleOp.getFnAttr(), sampleOp.getInputs(), subConstraint,
1852 subSelection, subConstrainedAddrs);
1853
1854 Value subTrace = nestedGenerate.getTrace();
1855 Value subWeight = nestedGenerate.getWeight();
1856
1857 weightAccumulator = arith::AddFOp::create(
1858 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
1859
1860 int64_t mergeOffset = computeOffsetForNestedSample(
1861 sampleOp, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
1862
1863 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
1864 auto row0 = arith::ConstantOp::create(
1865 rewriter, sampleOp.getLoc(), i64S,
1866 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
1867 auto colOff = arith::ConstantOp::create(
1868 rewriter, sampleOp.getLoc(), i64S,
1869 DenseElementsAttr::get(
1870 i64S, rewriter.getI64IntegerAttr(mergeOffset)));
1871 currTrace = impulse::DynamicUpdateSliceOp::create(
1872 rewriter, sampleOp.getLoc(), traceType, currTrace,
1873 subTrace, ValueRange{row0, colOff})
1874 .getResult();
1875 currentTraceOffset =
1876 std::max(currentTraceOffset, mergeOffset + subPositionSize);
1877
1878 for (auto output : nestedGenerate.getOutputs())
1879 sampledValues.push_back(output);
1880 }
1881 }
1882
1883 sampleOp.replaceAllUsesWith(sampledValues);
1884 toErase.push_back(sampleOp);
1885 return WalkResult::advance();
1886 });
1887
1888 for (Operation *op : toErase)
1889 rewriter.eraseOp(op);
1890
1891 if (result.wasInterrupted()) {
1892 CI.emitError("Impulse: failed to walk sample ops");
1893 return failure();
1894 }
1895
1896 // Rewrite the return to return (trace, weight, <original returns>...)
1897 NewF.walk([&](func::ReturnOp retOp) {
1898 OpBuilder::InsertionGuard guard(rewriter);
1899 rewriter.setInsertionPoint(retOp);
1900
1901 SmallVector<Value> newRetVals;
1902 newRetVals.push_back(currTrace);
1903 newRetVals.push_back(weightAccumulator);
1904 newRetVals.append(retOp.getOperands().begin(),
1905 retOp.getOperands().end());
1906
1907 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
1908 rewriter.eraseOp(retOp);
1909 });
1910
1911 rewriter.setInsertionPoint(CI);
1912 SmallVector<Value> operands;
1913 operands.push_back(CI.getConstraint());
1914 operands.append(CI.getInputs().begin(), CI.getInputs().end());
1915 auto newCI = func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
1916 NewF.getResultTypes(), operands);
1917
1918 rewriter.replaceOp(CI, newCI.getResults());
1919
1920 delete putils;
1921
1922 return success();
1923 }
1924 };
1925
1926 struct LowerRegeneratePattern
1927 : public mlir::OpRewritePattern<impulse::RegenerateOp> {
1928 using mlir::OpRewritePattern<impulse::RegenerateOp>::OpRewritePattern;
1929
1930 LogicalResult matchAndRewrite(impulse::RegenerateOp CI,
1931 PatternRewriter &rewriter) const override {
1932 SymbolTableCollection symbolTable;
1933
1934 auto fn = cast<FunctionOpInterface>(
1935 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
1936
1937 if (fn.getFunctionBody().empty()) {
1938 CI.emitError(
1939 "Impulse: calling `regenerate` on an empty function; if this "
1940 "is a distribution function, its sample op should have a "
1941 "logpdf attribute to avoid recursive `regenerate` calls which is "
1942 "intended for generative functions");
1943 return failure();
1944 }
1945
1946 ArrayAttr selection = CI.getSelectionAttr();
1947 int64_t positionSize =
1948 computePositionSizeForSelection(CI, fn, selection, symbolTable);
1949 if (positionSize <= 0) {
1950 CI.emitError("Impulse: failed to compute position size for regenerate");
1951 return failure();
1952 }
1953
1954 auto putils = ImpulseUtils::CreateFromClone(fn, ImpulseMode::Regenerate,
1955 positionSize);
1956 FunctionOpInterface NewF = putils->newFunc;
1957
1958 OpBuilder entryBuilder(putils->initializationBlock,
1959 putils->initializationBlock->begin());
1960 Location initLoc = putils->initializationBlock->begin()->getLoc();
1961
1962 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
1963 auto zeroWeight =
1964 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
1965 DenseElementsAttr::get(scalarType, 0.0));
1966 Value weightAccumulator = zeroWeight;
1967
1968 auto traceType =
1969 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
1970 auto zeroTrace =
1971 arith::ConstantOp::create(entryBuilder, initLoc, traceType,
1972 DenseElementsAttr::get(traceType, 0.0));
1973 Value currTrace = zeroTrace;
1974
1975 Value prevTrace = NewF.getArgument(0);
1976 int64_t currentTraceOffset = 0;
1977
1978 SmallVector<Operation *> toErase;
1979 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
1980 OpBuilder::InsertionGuard guard(rewriter);
1981 rewriter.setInsertionPoint(sampleOp);
1982
1983 SmallVector<Value> sampledValues;
1984 bool isDistribution = static_cast<bool>(sampleOp.getLogpdfAttr());
1985
1986 if (isDistribution) {
1987 // A1. Distribution function: call the distribution function.
1988 bool isSelected = false;
1989 for (auto addr : CI.getRegenerateAddressesAttr()) {
1990 auto address = cast<ArrayAttr>(addr);
1991 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
1992 if (address.size() != 1) {
1993 sampleOp.emitError(
1994 "Impulse: distribution function cannot have composite "
1995 "selected address");
1996 return WalkResult::interrupt();
1997 }
1998 isSelected = true;
1999 break;
2000 }
2001 }
2002
2003 int64_t sampleOffset = computeOffsetForSampleInSelection(
2004 CI, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
2005
2006 if (isSelected) {
2007 // A2. Regenerate: call the distribution function.
2008 auto distFn =
2009 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2010 sampleOp, sampleOp.getFnAttr()));
2011
2012 auto distCall = func::CallOp::create(
2013 rewriter, sampleOp.getLoc(), distFn.getName(),
2014 distFn.getResultTypes(), sampleOp.getInputs());
2015
2016 sampledValues.append(distCall.getResults().begin(),
2017 distCall.getResults().end());
2018 } else {
2019 // B. Generative function: extract from original trace.
2020 sampledValues.resize(sampleOp.getNumResults());
2021 sampledValues[0] = sampleOp.getOperand(0); // RNG state
2022
2023 int64_t extractOffset = sampleOffset;
2024 for (unsigned i = 1; i < sampleOp.getNumResults(); ++i) {
2025 auto resultType =
2026 cast<RankedTensorType>(sampleOp.getResult(i).getType());
2027 int64_t numElements = computeTensorElementCount(resultType);
2028 if (numElements < 0) {
2029 sampleOp.emitError(
2030 "Impulse: dynamic tensor dimensions not supported");
2031 return WalkResult::interrupt();
2032 }
2033
2034 auto sliceType = RankedTensorType::get(
2035 {1, numElements}, resultType.getElementType());
2036 auto sliced = impulse::SliceOp::create(
2037 rewriter, sampleOp.getLoc(), sliceType, prevTrace,
2038 rewriter.getDenseI64ArrayAttr({0, extractOffset}),
2039 rewriter.getDenseI64ArrayAttr(
2040 {1, extractOffset + numElements}),
2041 rewriter.getDenseI64ArrayAttr({1, 1}));
2042 auto extracted = impulse::ReshapeOp::create(
2043 rewriter, sampleOp.getLoc(), resultType, sliced);
2044 sampledValues[i] = extracted.getResult();
2045 extractOffset += numElements;
2046 }
2047 }
2048
2049 auto logpdfFn =
2050 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2051 sampleOp, sampleOp.getLogpdfAttr()));
2052
2053 SmallVector<Value> logpdfOperands;
2054 for (unsigned i = 1; i < sampledValues.size(); ++i) {
2055 logpdfOperands.push_back(sampledValues[i]);
2056 }
2057 for (unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
2058 logpdfOperands.push_back(sampleOp.getOperand(i));
2059 }
2060
2061 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
2062 sampleOp.emitError("Impulse: failed to construct logpdf call; "
2063 "logpdf function has wrong number of arguments");
2064 return WalkResult::interrupt();
2065 }
2066
2067 auto logpdf = func::CallOp::create(
2068 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
2069 logpdfFn.getResultTypes(), logpdfOperands);
2070 weightAccumulator =
2071 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
2072 weightAccumulator, logpdf.getResult(0));
2073
2074 bool inSelection = false;
2075 for (auto addr : selection) {
2076 auto address = cast<ArrayAttr>(addr);
2077 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
2078 inSelection = true;
2079 break;
2080 }
2081 }
2082
2083 if (inSelection) {
2084 for (unsigned i = 1; i < sampledValues.size(); ++i) {
2085 auto sampleValue = sampledValues[i];
2086 auto sampleType = cast<RankedTensorType>(sampleValue.getType());
2087 int64_t numElements = computeTensorElementCount(sampleType);
2088 if (numElements < 0) {
2089 sampleOp.emitError(
2090 "Impulse: dynamic tensor dimensions not supported");
2091 return WalkResult::interrupt();
2092 }
2093
2094 auto flatSampleType = RankedTensorType::get(
2095 {1, numElements}, sampleType.getElementType());
2096 auto flatSample = impulse::ReshapeOp::create(
2097 rewriter, sampleOp.getLoc(), flatSampleType, sampleValue);
2098 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
2099 auto row0 = arith::ConstantOp::create(
2100 rewriter, sampleOp.getLoc(), i64S,
2101 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
2102 auto colOff = arith::ConstantOp::create(
2103 rewriter, sampleOp.getLoc(), i64S,
2104 DenseElementsAttr::get(
2105 i64S, rewriter.getI64IntegerAttr(currentTraceOffset)));
2106 currTrace = impulse::DynamicUpdateSliceOp::create(
2107 rewriter, sampleOp.getLoc(), traceType, currTrace,
2108 flatSample, ValueRange{row0, colOff})
2109 .getResult();
2110 currentTraceOffset += numElements;
2111 }
2112 }
2113 } else {
2114 // B. Generative function: recursively regenerate the nested function
2115 auto genFn =
2116 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2117 sampleOp, sampleOp.getFnAttr()));
2118
2119 if (genFn.getFunctionBody().empty()) {
2120 sampleOp.emitError(
2121 "Impulse: generative function body is empty; "
2122 "if this is a distribution, add a logpdf attribute");
2123 return WalkResult::interrupt();
2124 }
2125
2126 ArrayAttr subSelection =
2127 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
2128 ArrayAttr subRegenerateAddrs =
2129 buildSubSelection(rewriter, CI.getRegenerateAddressesAttr(),
2130 sampleOp.getSymbolAttr());
2131
2132 if (subSelection.empty()) {
2133 auto genCall = func::CallOp::create(
2134 rewriter, sampleOp.getLoc(), genFn.getName(),
2135 genFn.getResultTypes(), sampleOp.getInputs());
2136 sampledValues.append(genCall.getResults().begin(),
2137 genCall.getResults().end());
2138 } else {
2139 int64_t subPositionSize = computePositionSizeForSelection(
2140 sampleOp, genFn, subSelection, symbolTable);
2141 if (subPositionSize <= 0) {
2142 sampleOp.emitError(
2143 "Impulse: failed to compute sub-position size");
2144 return WalkResult::interrupt();
2145 }
2146
2147 int64_t mergeOffset = computeOffsetForNestedSample(
2148 sampleOp, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
2149 if (mergeOffset < 0) {
2150 sampleOp.emitError("Impulse: failed to compute merge offset");
2151 return WalkResult::interrupt();
2152 }
2153
2154 auto subTraceType = RankedTensorType::get({1, subPositionSize},
2155 rewriter.getF64Type());
2156 Value subPrevTrace = impulse::SliceOp::create(
2157 rewriter, sampleOp.getLoc(), subTraceType, prevTrace,
2158 rewriter.getDenseI64ArrayAttr({0, mergeOffset}),
2159 rewriter.getDenseI64ArrayAttr(
2160 {1, mergeOffset + subPositionSize}),
2161 rewriter.getDenseI64ArrayAttr({1, 1}));
2162
2163 // Build result types: (new_trace, weight, original_returns...)
2164 auto scalarTy = RankedTensorType::get({}, rewriter.getF64Type());
2165 SmallVector<Type> regenResultTypes;
2166 regenResultTypes.push_back(subTraceType);
2167 regenResultTypes.push_back(scalarTy);
2168 for (auto t : genFn.getResultTypes())
2169 regenResultTypes.push_back(t);
2170
2171 auto nestedRegenerate = impulse::RegenerateOp::create(
2172 rewriter, sampleOp.getLoc(), regenResultTypes,
2173 sampleOp.getFnAttr(), sampleOp.getInputs(), subPrevTrace,
2174 subSelection, subRegenerateAddrs);
2175
2176 Value subTrace = nestedRegenerate.getNewTrace();
2177 Value subWeight = nestedRegenerate.getWeight();
2178
2179 weightAccumulator = arith::AddFOp::create(
2180 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
2181
2182 auto i64S = RankedTensorType::get({}, rewriter.getI64Type());
2183 auto row0 = arith::ConstantOp::create(
2184 rewriter, sampleOp.getLoc(), i64S,
2185 DenseElementsAttr::get(i64S, rewriter.getI64IntegerAttr(0)));
2186 auto colOff = arith::ConstantOp::create(
2187 rewriter, sampleOp.getLoc(), i64S,
2188 DenseElementsAttr::get(
2189 i64S, rewriter.getI64IntegerAttr(mergeOffset)));
2190 currTrace = impulse::DynamicUpdateSliceOp::create(
2191 rewriter, sampleOp.getLoc(), traceType, currTrace,
2192 subTrace, ValueRange{row0, colOff})
2193 .getResult();
2194 currentTraceOffset =
2195 std::max(currentTraceOffset, mergeOffset + subPositionSize);
2196
2197 for (auto output : nestedRegenerate.getOutputs())
2198 sampledValues.push_back(output);
2199 }
2200 }
2201
2202 sampleOp.replaceAllUsesWith(sampledValues);
2203 toErase.push_back(sampleOp);
2204 return WalkResult::advance();
2205 });
2206
2207 for (Operation *op : toErase)
2208 rewriter.eraseOp(op);
2209
2210 if (result.wasInterrupted()) {
2211 CI.emitError("Impulse: failed to walk sample ops");
2212 return failure();
2213 }
2214
2215 NewF.walk([&](func::ReturnOp retOp) {
2216 OpBuilder::InsertionGuard guard(rewriter);
2217 rewriter.setInsertionPoint(retOp);
2218
2219 SmallVector<Value> newRetVals;
2220 newRetVals.push_back(currTrace);
2221 newRetVals.push_back(weightAccumulator);
2222 newRetVals.append(retOp.getOperands().begin(),
2223 retOp.getOperands().end());
2224
2225 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
2226 rewriter.eraseOp(retOp);
2227 });
2228
2229 rewriter.setInsertionPoint(CI);
2230 SmallVector<Value> operands;
2231 operands.push_back(CI.getOriginalTrace());
2232 operands.append(CI.getInputs().begin(), CI.getInputs().end());
2233 auto newCI = func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
2234 NewF.getResultTypes(), operands);
2235
2236 rewriter.replaceOp(CI, newCI.getResults());
2237
2238 delete putils;
2239
2240 return success();
2241 }
2242 };
2243};
2244
2245} // end anonymous namespace
2246
2247void ExpandImpulsePass::runOnOperation() {
2248 RewritePatternSet patterns(&getContext());
2249 patterns.add<LowerUntracedCallPattern, LowerSimulatePattern,
2250 LowerGeneratePattern, LowerMHPattern, LowerRegeneratePattern>(
2251 &getContext());
2252 patterns.add<LowerMCMCPattern>(&getContext(), debugDump);
2253
2254 mlir::GreedyRewriteConfig config;
2255
2256 if (failed(
2257 applyPatternsGreedily(getOperation(), std::move(patterns), config))) {
2258 signalPassFailure();
2259 return;
2260 }
2261
2262 if (!postpasses.empty()) {
2263 mlir::PassManager pm(getOperation()->getContext());
2264
2265 if (mlir::failed(mlir::parsePassPipeline(postpasses, pm))) {
2266 getOperation()->emitError()
2267 << "Failed to parse expand-impulse post-passes pipeline: "
2268 << postpasses;
2269 signalPassFailure();
2270 return;
2271 }
2272
2273 if (mlir::failed(pm.run(getOperation()))) {
2274 signalPassFailure();
2275 return;
2276 }
2277 }
2278}
PointerType * traceType(LLVMContext &C)
static ImpulseUtils * CreateFromClone(FunctionOpInterface toeval, ImpulseMode mode, int64_t positionSize=-1, int64_t constraintSize=-1)
Value finalizeWelford(OpBuilder &builder, Location loc, const WelfordState &state, const WelfordConfig &config)
Finalize Welford state to produce sample covariance (returned as inverse mass matrix).
DualAveragingState initDualAveraging(OpBuilder &builder, Location loc, Value stepSize, bool logOfProduct=false)
Initialize dual averaging state from initial step size.
SmallVector< AdaptWindow > buildAdaptationSchedule(int64_t numSteps)
Build warmup adaptation schedule.
Value conditionalDump(OpBuilder &builder, Location loc, Value value, StringRef label, bool debugDump)
Conditionally dump a value for debugging.
Definition HMCUtils.cpp:64
MCMCKernelResult SampleNUTS(OpBuilder &builder, Location loc, Value q, Value grad, Value U, Value rng, const NUTSContext &ctx, bool debugDump=false)
Single NUTS iteration: momentum sampling + tree building.
InitialHMCState InitHMC(OpBuilder &builder, Location loc, Value rng, const HMCContext &ctx, Value initialPosition=Value(), bool debugDump=false)
Initializes HMC/NUTS state from a trace Specifically:
Definition HMCUtils.cpp:701
DualAveragingState updateDualAveraging(OpBuilder &builder, Location loc, const DualAveragingState &state, Value acceptProb, const DualAveragingConfig &config)
Update dual averaging state with observed acceptance probability.
Value computeMassMatrixSqrt(OpBuilder &builder, Location loc, Value invMass, RankedTensorType positionType)
Computes the square root of the mass matrix from the inverse mass matrix.
Definition HMCUtils.cpp:190
WelfordState updateWelford(OpBuilder &builder, Location loc, const WelfordState &state, Value sample, const WelfordConfig &config)
Update Welford state with a new sample.
WelfordState initWelford(OpBuilder &builder, Location loc, int64_t positionSize, bool diagonal)
Initialize state for Welford covariance estimation.
Value getStepSizeFromDualAveraging(OpBuilder &builder, Location loc, const DualAveragingState &state, bool final=false)
Get step size from dual averaging state.
MCMCKernelResult SampleHMC(OpBuilder &builder, Location loc, Value q, Value grad, Value U, Value rng, const HMCContext &ctx, bool debugDump=false)
Single HMC iteration: momentum sampling + leapfrog + MH accept/reject.
Definition HMCUtils.cpp:862
Value constrainPosition(OpBuilder &builder, Location loc, Value unconstrained, ArrayRef< SupportInfo > supports)
Transform an entire position vector from unconstrained to constrained space.
SmallVector< Type > getTypes() const
Definition HMCUtils.h:87
static DualAveragingState fromValues(ArrayRef< Value > values)
Definition HMCUtils.h:84
SmallVector< Value > toValues() const
Definition HMCUtils.h:80
Result of one MCMC kernel step.
Definition HMCUtils.h:62
Configuration for Welford covariance estimation.
Definition HMCUtils.h:422
State for Welford covariance estimation.
Definition HMCUtils.h:407
SmallVector< Value > toValues() const
Definition HMCUtils.h:412
SmallVector< Type > getTypes() const
Definition HMCUtils.h:416
static WelfordState fromValues(ArrayRef< Value > values)
Definition HMCUtils.h:413