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"
29#include "llvm/ADT/APFloat.h"
31#define DEBUG_TYPE "expand-impulse"
39#define GEN_PASS_DEF_EXPANDIMPULSEPASS
40#include "Passes/Passes.h.inc"
46static int64_t computeTensorElementCount(RankedTensorType tensorType) {
47 int64_t elemCount = 1;
48 for (
auto dim : tensorType.getShape()) {
49 if (dim == ShapedType::kDynamic)
56using SampleOpMap = DenseMap<Attribute, impulse::SampleOp>;
58static SampleOpMap buildSampleOpMap(FunctionOpInterface fn) {
60 fn.walk([&](impulse::SampleOp sampleOp) {
61 if (
auto symbol = sampleOp.getSymbolAttr())
62 map[symbol] = sampleOp;
67static impulse::SampleOp findSampleBySymbol(
const SampleOpMap &map,
68 Attribute targetSymbol) {
69 auto it = map.find(targetSymbol);
70 return it != map.end() ? it->second :
nullptr;
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);
80 op->emitError(
"Expected ranked tensor type for sample result");
83 int64_t elemCount = computeTensorElementCount(tensorType);
85 op->emitError(
"Dynamic tensor dimensions not supported");
88 totalCount += elemCount;
93static bool computePositionSizeForAddress(Operation *op,
94 const SampleOpMap &sampleMap,
95 ArrayRef<Attribute> address,
96 SymbolTableCollection &symbolTable,
97 int64_t &positionSize) {
101 auto sampleOp = findSampleBySymbol(sampleMap, address[0]);
105 if (address.size() > 1) {
106 if (sampleOp.getLogpdfAttr()) {
107 op->emitError(
"Cannot select nested address in distribution function");
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");
118 auto nestedMap = buildSampleOpMap(genFn);
119 return computePositionSizeForAddress(op, nestedMap, address.drop_front(),
120 symbolTable, positionSize);
123 int64_t elemCount = computeSampleElementCount(op, sampleOp);
127 positionSize += elemCount;
132computePositionSizeForSelection(Operation *op, FunctionOpInterface fn,
134 SymbolTableCollection &symbolTable) {
135 auto sampleMap = buildSampleOpMap(fn);
136 int64_t positionSize = 0;
138 for (
auto addr : selection) {
139 auto address = cast<ArrayAttr>(addr);
140 if (address.empty()) {
141 op->emitError(
"Empty address in selection");
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");
157computeOffsetForSampleInSelection(Operation *op, FunctionOpInterface fn,
158 ArrayAttr selection, Attribute targetSymbol,
159 SymbolTableCollection &symbolTable) {
160 auto sampleMap = buildSampleOpMap(fn);
163 for (
auto addr : selection) {
164 auto address = cast<ArrayAttr>(addr);
168 auto firstSymbol = address[0];
170 if (firstSymbol == targetSymbol) {
174 SmallVector<Attribute> tailAddresses(address.begin(), address.end());
175 if (!computePositionSizeForAddress(op, sampleMap, tailAddresses,
176 symbolTable, offset)) {
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;
192 for (
auto addr : selection) {
193 auto address = cast<ArrayAttr>(addr);
198 if (address.size() != 1)
201 auto targetSymbol = address[0];
202 auto sampleOp = findSampleBySymbol(sampleMap, targetSymbol);
206 auto supportAttr = sampleOp.getSupportAttr();
208 int64_t sampleSize = computeSampleElementCount(op, sampleOp);
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");
220 supports.emplace_back(currentPositionOffset, traceOffset, sampleSize,
222 currentPositionOffset += sampleSize;
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);
235 if (address[0] == targetSymbol && address.size() > 1) {
236 SmallVector<Attribute> tail(address.begin() + 1, address.end());
237 subAddresses.push_back(builder.getArrayAttr(tail));
240 return builder.getArrayAttr(subAddresses);
244computeOffsetForNestedSample(Operation *op, FunctionOpInterface fn,
245 ArrayAttr selection, Attribute targetSymbol,
246 SymbolTableCollection &symbolTable) {
247 auto sampleMap = buildSampleOpMap(fn);
250 for (
auto addr : selection) {
251 auto address = cast<ArrayAttr>(addr);
255 if (address[0] == targetSymbol) {
259 SmallVector<Attribute> tailAddresses(address.begin(), address.end());
260 if (!computePositionSizeForAddress(op, sampleMap, tailAddresses,
261 symbolTable, offset)) {
269struct ExpandImpulsePass
270 :
public enzyme::impl::ExpandImpulsePassBase<ExpandImpulsePass> {
271 using ExpandImpulsePassBase::ExpandImpulsePassBase;
273 void runOnOperation()
override;
275 void getDependentDialects(DialectRegistry ®istry)
const override {
276 mlir::OpPassManager pm;
277 mlir::LogicalResult result = mlir::parsePassPipeline(postpasses, pm);
278 if (!mlir::failed(result)) {
279 pm.getDependentDialects(registry);
283 .insert<mlir::arith::ArithDialect, mlir::math::MathDialect,
284 mlir::complex::ComplexDialect, mlir::cf::ControlFlowDialect,
285 mlir::enzyme::EnzymeDialect, mlir::impulse::ImpulseDialect>();
288 struct LowerUntracedCallPattern
289 :
public mlir::OpRewritePattern<impulse::UntracedCallOp> {
292 LogicalResult matchAndRewrite(impulse::UntracedCallOp CI,
293 PatternRewriter &rewriter)
const override {
294 SymbolTableCollection symbolTable;
296 auto fn = cast<FunctionOpInterface>(
297 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
299 if (fn.getFunctionBody().empty()) {
300 CI.emitError(
"Impulse: trying to call an empty function");
305 FunctionOpInterface NewF = putils->newFunc;
307 SmallVector<Operation *, 4> toErase;
308 NewF.walk([&](impulse::SampleOp sampleOp) {
309 OpBuilder::InsertionGuard guard(rewriter);
310 rewriter.setInsertionPoint(sampleOp);
313 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
314 sampleOp, sampleOp.getFnAttr()));
316 func::CallOp::create(rewriter, sampleOp.getLoc(), distFn.getName(),
317 distFn.getResultTypes(), sampleOp.getInputs());
318 sampleOp.replaceAllUsesWith(distCall);
320 toErase.push_back(sampleOp);
323 for (Operation *op : toErase)
324 rewriter.eraseOp(op);
326 rewriter.setInsertionPoint(CI);
328 func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
329 NewF.getResultTypes(), CI.getOperands());
331 rewriter.replaceOp(CI, newCI.getResults());
339 struct LowerSimulatePattern
340 :
public mlir::OpRewritePattern<impulse::SimulateOp> {
343 LogicalResult matchAndRewrite(impulse::SimulateOp CI,
344 PatternRewriter &rewriter)
const override {
345 SymbolTableCollection symbolTable;
347 auto fn = cast<FunctionOpInterface>(
348 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
350 if (fn.getFunctionBody().empty()) {
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");
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");
369 FunctionOpInterface NewF = putils->newFunc;
371 OpBuilder entryBuilder(putils->initializationBlock,
372 putils->initializationBlock->begin());
373 Location initLoc = putils->initializationBlock->begin()->getLoc();
374 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
376 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
377 DenseElementsAttr::get(scalarType, 0.0));
378 Value weightAccumulator = zeroWeight;
381 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
383 arith::ConstantOp::create(entryBuilder, initLoc,
traceType,
385 Value currTrace = zeroTrace;
386 int64_t currentOffset = 0;
388 SmallVector<Operation *> toErase;
389 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
390 OpBuilder::InsertionGuard guard(rewriter);
391 rewriter.setInsertionPoint(sampleOp);
393 SmallVector<Value> sampledValues;
394 bool isDistribution =
static_cast<bool>(sampleOp.getLogpdfAttr());
396 if (isDistribution) {
399 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
400 sampleOp, sampleOp.getFnAttr()));
402 auto distCall = func::CallOp::create(
403 rewriter, sampleOp.getLoc(), distFn.getName(),
404 distFn.getResultTypes(), sampleOp.getInputs());
406 sampledValues.append(distCall.getResults().begin(),
407 distCall.getResults().end());
410 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
411 sampleOp, sampleOp.getLogpdfAttr()));
414 SmallVector<Value> logpdfOperands;
415 for (
unsigned i = 1; i < sampledValues.size(); ++i) {
416 logpdfOperands.push_back(sampledValues[i]);
418 for (
unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
419 logpdfOperands.push_back(sampleOp.getOperand(i));
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();
429 auto logpdf = func::CallOp::create(
430 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
431 logpdfFn.getResultTypes(), logpdfOperands);
433 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
434 weightAccumulator, logpdf.getResult(0));
437 bool inSelection =
false;
438 for (
auto addr : selection) {
439 auto address = cast<ArrayAttr>(addr);
440 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
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) {
453 "Impulse: dynamic tensor dimensions not supported");
454 return WalkResult::interrupt();
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})
473 currentOffset += numElements;
479 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
480 sampleOp, sampleOp.getFnAttr()));
482 if (genFn.getFunctionBody().empty()) {
484 "Impulse: generative function body is empty; "
485 "if this is a distribution, add a logpdf attribute");
486 return WalkResult::interrupt();
489 ArrayAttr subSelection =
490 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
491 if (subSelection.empty()) {
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());
500 int64_t subPositionSize = computePositionSizeForSelection(
501 sampleOp, genFn, subSelection, symbolTable);
502 if (subPositionSize <= 0) {
504 "Impulse: failed to compute sub-position size");
505 return WalkResult::interrupt();
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);
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();
524 weightAccumulator = arith::AddFOp::create(
525 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
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();
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})
547 std::max(currentOffset, mergeOffset + subPositionSize);
549 for (
auto output : nestedSimulate.getOutputs())
550 sampledValues.push_back(output);
555 sampleOp.replaceAllUsesWith(sampledValues);
557 toErase.push_back(sampleOp);
558 return WalkResult::advance();
561 for (Operation *op : toErase)
562 rewriter.eraseOp(op);
564 if (result.wasInterrupted()) {
565 CI.emitError(
"Impulse: failed to walk sample ops");
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());
579 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
580 rewriter.eraseOp(retOp);
583 rewriter.setInsertionPoint(CI);
584 auto newCI = func::CallOp::create(rewriter, CI.getLoc(), NewF.getName(),
585 NewF.getResultTypes(), CI.getInputs());
587 rewriter.replaceOp(CI, newCI.getResults());
595 struct LowerMCMCPattern :
public mlir::OpRewritePattern<impulse::InferOp> {
598 LowerMCMCPattern(MLIRContext *context,
bool debugDump,
599 PatternBenefit benefit = 1)
602 LogicalResult matchAndRewrite(impulse::InferOp mcmcOp,
603 PatternRewriter &rewriter)
const override {
604 SymbolTableCollection symbolTable;
606 bool hasLogpdfFn =
static_cast<bool>(mcmcOp.getLogpdfFnAttr());
609 auto fnAttr = mcmcOp.getFnAttr();
611 mcmcOp.emitError(
"Impulse: either fn or logpdf_fn must be provided");
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");
622 if (!mcmcOp.getStepSize()) {
623 mcmcOp.emitError(
"Impulse: MCMC requires step_size parameter");
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");
634 auto loc = mcmcOp.getLoc();
635 auto invMass = mcmcOp.getInverseMassMatrix();
636 Value adaptedInvMass = invMass;
637 auto stepSize = mcmcOp.getStepSize();
639 auto inputs = mcmcOp.getInputs();
640 if (inputs.empty()) {
641 mcmcOp.emitError(
"Impulse: MCMC requires at least rng_state input");
645 auto rngInput = inputs[0];
647 int64_t positionSize;
648 SmallVector<Value> fnInputs;
649 SmallVector<Type> fnResultTypes;
651 ArrayAttr selection, allAddresses;
652 SmallVector<SupportInfo> supports;
653 FlatSymbolRefAttr logpdfFnAttr;
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();
664 fnInputs.assign(inputs.begin() + 1, inputs.end());
665 originalTrace = mcmcOp.getOriginalTrace();
666 selection = mcmcOp.getSelectionAttr();
667 allAddresses = mcmcOp.getAllAddressesAttr();
669 auto fn = cast<FunctionOpInterface>(
670 symbolTable.lookupNearestSymbolFrom(mcmcOp, mcmcOp.getFnAttr()));
672 computePositionSizeForSelection(mcmcOp, fn, selection, symbolTable);
673 if (positionSize <= 0)
676 supports = collectSupportInfoForSelection(mcmcOp, fn, selection,
677 allAddresses, symbolTable);
679 auto fnType = cast<FunctionType>(fn.getFunctionType());
680 fnResultTypes.assign(fnType.getResults().begin(),
681 fnType.getResults().end());
684 int64_t numSamples = mcmcOp.getNumSamples();
685 int64_t thinning = mcmcOp.getThinning();
686 int64_t numWarmup = mcmcOp.getNumWarmup();
688 int64_t totalWarmup = numWarmup;
689 if (
auto tw = mcmcOp.getTotalWarmup())
691 bool exposeAdaptation = !mcmcOp.getAdaptationStateOut().empty();
692 SmallVector<Value> adaptationInVals(mcmcOp.getAdaptationStateIn().begin(),
693 mcmcOp.getAdaptationStateIn().end());
694 bool hasAdaptationIn = !adaptationInVals.empty();
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());
704 Value trajectoryLength;
705 Value maxDeltaEnergy;
706 int64_t maxTreeDepth = 0;
708 bool adaptStepSize =
false;
709 bool adaptMassMatrix =
false;
710 auto F64TensorType = RankedTensorType::get({}, rewriter.getF64Type());
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();
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()
729 maxDeltaEnergy = arith::ConstantOp::create(
730 rewriter, loc, F64TensorType,
731 DenseElementsAttr::get(
732 F64TensorType, rewriter.getF64FloatAttr(maxDeltaEnergyVal)));
735 bool diagonal =
true;
737 auto invMassType = cast<RankedTensorType>(invMass.getType());
738 diagonal = (invMassType.getRank() == 1);
741 auto adaptedMassMatrixSqrt =
744 auto autodiffAttrs = mcmcOp.getAutodiffAttrsAttr();
746 auto makeHMCContext = [&](Value currentInvMass,
747 Value currentMassMatrixSqrt,
750 return HMCContext(logpdfFnAttr, fnInputs, currentInvMass,
751 currentMassMatrixSqrt, currentStepSize,
752 trajectoryLength, positionSize, autodiffAttrs);
754 return HMCContext(mcmcOp.getFnAttr(), fnInputs, fnResultTypes,
755 originalTrace, selection, allAddresses,
756 currentInvMass, currentMassMatrixSqrt,
757 currentStepSize, trajectoryLength, positionSize,
758 supports, autodiffAttrs);
762 auto makeNUTSContext =
763 [&](Value currentInvMass, Value currentMassMatrixSqrt,
766 return NUTSContext(logpdfFnAttr, fnInputs, currentInvMass,
767 currentMassMatrixSqrt, currentStepSize,
768 positionSize, U, maxDeltaEnergy, maxTreeDepth,
771 return NUTSContext(mcmcOp.getFnAttr(), fnInputs, fnResultTypes,
772 originalTrace, selection, allAddresses,
773 currentInvMass, currentMassMatrixSqrt,
774 currentStepSize, positionSize, supports, U,
775 maxDeltaEnergy, maxTreeDepth, autodiffAttrs);
779 Value currentQ, currentGrad, currentU, currentRng;
781 auto initialGrad = mcmcOp.getInitialGradient();
782 auto initialPE = mcmcOp.getInitialPotentialEnergy();
784 if (hasLogpdfFn && initialGrad && initialPE) {
785 currentQ = mcmcOp.getInitialPosition();
786 currentGrad = initialGrad;
787 currentU = initialPE;
788 currentRng = rngInput;
791 makeHMCContext(adaptedInvMass, adaptedMassMatrixSqrt, stepSize);
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;
801 auto runSampleStepWithStepSize =
802 [&](OpBuilder &builder, Location loc, Value q, Value grad, Value U,
805 auto ctx = makeHMCContext(adaptedInvMass, adaptedMassMatrixSqrt,
807 return SampleHMC(builder, loc, q, grad, U, rng, ctx, debugDump);
809 auto nutsCtx = makeNUTSContext(adaptedInvMass, adaptedMassMatrixSqrt,
811 return SampleNUTS(builder, loc, q, grad, U, rng, nutsCtx, debugDump);
815 Value adaptedStepSize = stepSize;
817 auto runSampleStepWithInvMass =
818 [&](OpBuilder &builder, Location loc, Value q, Value grad, Value U,
819 Value rng, Value currentStepSize, Value currentInvMass,
822 auto ctx = makeHMCContext(currentInvMass, currentMassMatrixSqrt,
824 return SampleHMC(builder, loc, q, grad, U, rng, ctx, debugDump);
826 auto nutsCtx = makeNUTSContext(currentInvMass, currentMassMatrixSqrt,
828 return SampleNUTS(builder, loc, q, grad, U, rng, nutsCtx, debugDump);
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)));
843 SmallVector<Value> finalAdaptationState;
846 Value widx) -> SmallVector<Value> {
847 SmallVector<Value> v;
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)));
872 int64_t numWindows =
static_cast<int64_t
>(schedule.size());
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))));
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)));
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)));
902 Value initialStepSize = stepSize;
905 "MCMC: initial step size before warmup", debugDump);
909 ArrayRef<Value>(adaptationInVals).take_front(5))
915 if (adaptMassMatrix) {
919 ArrayRef<Value>(adaptationInVals).slice(5, 3))
920 :
initWelford(rewriter, loc, positionSize, diagonal);
927 ? adaptationInVals[8]
928 : arith::ConstantOp::create(
929 rewriter, loc, i64TensorType,
930 DenseElementsAttr::get(i64TensorType,
931 rewriter.getI64IntegerAttr(0)));
936 SmallVector<Type> warmupLoopTypes = {positionType,
939 currentRng.getType(),
941 adaptedInvMass.getType(),
942 adaptedMassMatrixSqrt.getType()};
944 warmupLoopTypes.push_back(t);
945 if (adaptMassMatrix) {
946 for (Type t : welfordState.
getTypes())
947 warmupLoopTypes.push_back(t);
949 warmupLoopTypes.push_back(i64TensorType);
951 SmallVector<Value> warmupInitArgs = {currentQ,
957 adaptedMassMatrixSqrt};
959 warmupInitArgs.push_back(v);
960 if (adaptMassMatrix) {
961 for (Value v : welfordState.
toValues())
962 warmupInitArgs.push_back(v);
964 warmupInitArgs.push_back(windowIdx);
967 impulse::ForOp::create(rewriter, loc, warmupLoopTypes, c0,
968 numWarmupConst, c1, warmupInitArgs);
970 Block *warmupBody = rewriter.createBlock(&warmupLoop.getRegion());
971 warmupBody->addArgument(i64TensorType, loc);
972 for (Type t : warmupLoopTypes)
973 warmupBody->addArgument(t, loc);
975 rewriter.setInsertionPointToStart(warmupBody);
977 Value iterT = warmupBody->getArgument(0);
978 Value iterTGlobal = iterT;
979 if (Value warmupOffset = mcmcOp.getWarmupOffset())
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);
990 SmallVector<Value> daStateLoopValues;
991 for (
int i = 0; i < 5; ++i)
992 daStateLoopValues.push_back(warmupBody->getArgument(8 + i));
997 if (adaptMassMatrix) {
998 SmallVector<Value> welfordStateLoopValues;
999 for (
int i = 0; i < 3; ++i)
1000 welfordStateLoopValues.push_back(warmupBody->getArgument(13 + i));
1002 windowIdxLoop = warmupBody->getArgument(16);
1004 windowIdxLoop = warmupBody->getArgument(13);
1007 auto sample = runSampleStepWithInvMass(rewriter, loc, qLoop, gradLoop,
1008 ULoop, rngLoop, stepSizeLoop,
1009 invMassLoop, massMatrixSqrtLoop);
1014 Value currentStepSizeFromDA;
1015 Value finalStepSizeFromDA;
1017 if (adaptStepSize) {
1019 sample.accept_prob, daConfig);
1020 currentStepSizeFromDA =
1022 finalStepSizeFromDA =
1025 updatedDaState = daStateLoop;
1026 currentStepSizeFromDA = stepSizeLoop;
1027 finalStepSizeFromDA = stepSizeLoop;
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);
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(
1045 auto maxConst = arith::ConstantOp::create(
1046 rewriter, loc, scalarType,
1047 DenseElementsAttr::get(
1048 scalarType, FloatAttr::get(elemType, llvm::APFloat::getLargest(
1050 adaptedStepSizeInLoop = arith::MaximumFOp::create(
1051 rewriter, loc, adaptedStepSizeInLoop, tinyConst);
1052 adaptedStepSizeInLoop = arith::MinimumFOp::create(
1053 rewriter, loc, adaptedStepSizeInLoop, maxConst);
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);
1065 if (adaptMassMatrix) {
1066 auto sampleType1D = RankedTensorType::get({positionSize}, elemType);
1068 impulse::ReshapeOp::create(rewriter, loc, sampleType1D, sample.q);
1070 rewriter, loc, welfordStateLoop, sample1D, welfordConfig);
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);
1083 Value atWindowEnd = arith::ConstantOp::create(
1084 rewriter, loc, i1TensorType,
1085 DenseElementsAttr::get(i1TensorType, rewriter.getBoolAttr(
false)));
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,
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);
1109 auto atMiddleWindowEnd =
1110 arith::AndIOp::create(rewriter, loc, atWindowEnd, isMiddleWindow);
1113 Value finalMassMatrixSqrt;
1115 Value finalStepSizeValue;
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());
1126 for (Type t : updatedDaState.
getTypes())
1127 ifResultTypes.push_back(t);
1129 auto ifOp = impulse::IfOp::create(rewriter, loc, ifResultTypes,
1133 Block *trueBranch = rewriter.createBlock(&ifOp.getTrueBranch());
1134 rewriter.setInsertionPointToStart(trueBranch);
1136 SmallVector<Value> trueYieldValues;
1138 if (adaptMassMatrix) {
1141 auto newMassMatrixSqrt =
1143 auto reinitWelford =
1144 initWelford(rewriter, loc, positionSize, diagonal);
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);
1152 trueYieldValues.push_back(invMassLoop);
1153 trueYieldValues.push_back(massMatrixSqrtLoop);
1156 if (adaptStepSize) {
1157 auto reinitDaState =
1159 for (
auto v : reinitDaState.toValues())
1160 trueYieldValues.push_back(v);
1162 for (
auto v : updatedDaState.
toValues())
1163 trueYieldValues.push_back(v);
1166 impulse::YieldOp::create(rewriter, loc, trueYieldValues);
1170 Block *falseBranch = rewriter.createBlock(&ifOp.getFalseBranch());
1171 rewriter.setInsertionPointToStart(falseBranch);
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);
1181 for (
auto v : updatedDaState.
toValues())
1182 falseYieldValues.push_back(v);
1184 impulse::YieldOp::create(rewriter, loc, falseYieldValues);
1187 rewriter.setInsertionPointAfter(ifOp);
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++);
1199 finalDaState.
gradient_avg = ifOp.getResult(resultIdx++);
1200 finalDaState.
step_count = ifOp.getResult(resultIdx++);
1201 finalDaState.
prox_center = ifOp.getResult(resultIdx++);
1203 finalStepSizeValue = adaptedStepSizeInLoop;
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);
1214 warmupYieldValues.push_back(windowIdxAfterIncrement);
1216 impulse::YieldOp::create(rewriter, loc, warmupYieldValues);
1218 rewriter.setInsertionPointAfter(warmupLoop);
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);
1230 "MCMC: adapted step size after warmup", debugDump);
1231 if (adaptMassMatrix) {
1233 rewriter, loc, adaptedInvMass,
1234 "MCMC: adapted inverse mass matrix after warmup", debugDump);
1237 if (exposeAdaptation) {
1239 {warmupLoop.getResult(7), warmupLoop.getResult(8),
1240 warmupLoop.getResult(9), warmupLoop.getResult(10),
1241 warmupLoop.getResult(11)});
1244 if (adaptMassMatrix) {
1246 warmupLoop.getResult(13),
1247 warmupLoop.getResult(14)});
1248 finalWidx = warmupLoop.getResult(15);
1250 finalW =
initWelford(rewriter, loc, positionSize, diagonal);
1251 finalWidx = warmupLoop.getResult(12);
1253 finalAdaptationState = packAdaptation(finalDa, finalW, finalWidx);
1257 if (exposeAdaptation && finalAdaptationState.empty()) {
1261 ArrayRef<Value>(adaptationInVals).take_front(5))
1267 ArrayRef<Value>(adaptationInVals).slice(5, 3))
1268 :
initWelford(rewriter, loc, positionSize, diagonal);
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);
1279 int64_t collectionSize = numSamples / thinning;
1280 int64_t startIdx = numSamples % thinning;
1282 auto samplesBufferType =
1283 RankedTensorType::get({collectionSize, positionSize}, elemType);
1284 auto diagnosticsBufferType =
1286 RankedTensorType::get({collectionSize, 2}, rewriter.getI1Type());
1287 auto logDensityBufferType =
1288 RankedTensorType::get({collectionSize}, elemType);
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)));
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)));
1322 Value finalQ, finalGrad, finalU, finalRng, finalSamplesBuffer,
1323 finalDiagnosticsBuffer, finalLogDensityBuffer;
1324 if (collectionSize == 0) {
1326 finalGrad = currentGrad;
1328 finalRng = currentRng;
1329 finalSamplesBuffer = samplesBuffer;
1330 finalDiagnosticsBuffer = diagnosticsBuffer;
1331 finalLogDensityBuffer = 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});
1344 Block *loopBody = rewriter.createBlock(&forLoopOp.getRegion());
1345 loopBody->addArgument(i64TensorType, loc);
1346 loopBody->addArgument(positionType, loc);
1347 loopBody->addArgument(positionType, loc);
1348 loopBody->addArgument(scalarType, loc);
1349 loopBody->addArgument(currentRng.getType(), loc);
1350 loopBody->addArgument(samplesBufferType, loc);
1351 loopBody->addArgument(diagnosticsBufferType, loc);
1352 loopBody->addArgument(logDensityBufferType, loc);
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);
1364 auto sample = runSampleStepWithStepSize(
1365 rewriter, loc, qLoop, gradLoop, ULoop, rngLoop, adaptedStepSize);
1366 auto q_constrained =
1371 arith::SubIOp::create(rewriter, loc, iterIdx, startIdxConst);
1373 arith::DivSIOp::create(rewriter, loc, iMinusStart, thinningConst);
1377 auto geStartIdx = arith::CmpIOp::create(
1378 rewriter, loc, arith::CmpIPredicate::sge, iterIdx, startIdxConst);
1380 arith::RemSIOp::create(rewriter, loc, iMinusStart, thinningConst);
1381 auto modIsZero = arith::CmpIOp::create(
1382 rewriter, loc, arith::CmpIPredicate::eq, modThinning, c0);
1384 arith::AndIOp::create(rewriter, loc, geStartIdx, modIsZero);
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,
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()),
1404 auto divergent1x1 = impulse::ReshapeOp::create(
1405 rewriter, loc, RankedTensorType::get({1, 1}, rewriter.getI1Type()),
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);
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);
1427 impulse::YieldOp::create(rewriter, loc,
1428 ValueRange{sample.q, sample.grad, sample.U,
1429 sample.rng, selectedSamplesBuffer,
1430 selectedDiagnosticsBuffer,
1431 selectedLogDensityBuffer});
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);
1443 finalSamplesBuffer =
1445 "MCMC: collected samples", debugDump);
1447 SmallVector<Value> replacements = {finalSamplesBuffer,
1448 finalDiagnosticsBuffer,
1449 finalLogDensityBuffer,
1456 if (exposeAdaptation)
1457 replacements.append(finalAdaptationState.begin(),
1458 finalAdaptationState.end());
1459 rewriter.replaceOp(mcmcOp, replacements);
1465 struct LowerMHPattern :
public mlir::OpRewritePattern<impulse::MHOp> {
1468 LogicalResult matchAndRewrite(impulse::MHOp mhOp,
1469 PatternRewriter &rewriter)
const override {
1470 SymbolTableCollection symbolTable;
1472 auto fn = cast<FunctionOpInterface>(
1473 symbolTable.lookupNearestSymbolFrom(mhOp, mhOp.getFnAttr()));
1475 if (fn.getFunctionBody().empty()) {
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");
1484 auto loc = mhOp.getLoc();
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();
1494 auto weightType = cast<RankedTensorType>(oldWeight.getType());
1495 auto rngStateType = inputs[0].getType();
1498 auto nameAttr = mhOp.getNameAttr();
1500 nameAttr = rewriter.getStringAttr(
"");
1502 auto regenerateAddresses = mhOp.getRegenerateAddressesAttr();
1504 SmallVector<Type> regenResultTypes;
1506 regenResultTypes.push_back(weightType);
1507 for (
auto t : fn.getResultTypes())
1508 regenResultTypes.push_back(t);
1510 auto regenerateOp = rewriter.create<impulse::RegenerateOp>(
1517 regenerateAddresses,
1520 Value newTrace = regenerateOp.getNewTrace();
1521 Value newWeight = regenerateOp.getWeight();
1522 Value newRng = regenerateOp.getOutputs()[0];
1526 arith::SubFOp::create(rewriter, loc, newWeight, oldWeight);
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));
1534 auto randomOp = impulse::RandomOp::create(
1535 rewriter, loc, TypeRange{rngStateType, weightType}, newRng, zeroConst,
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();
1543 auto accepted = arith::CmpFOp::create(
1544 rewriter, loc, arith::CmpFPredicate::OLT, logRand, logAlpha);
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);
1552 rewriter.replaceOp(mhOp,
1553 {selectedTrace, selectedWeight, accepted, finalRng});
1558 struct LowerGeneratePattern
1559 :
public mlir::OpRewritePattern<impulse::GenerateOp> {
1562 LogicalResult matchAndRewrite(impulse::GenerateOp CI,
1563 PatternRewriter &rewriter)
const override {
1564 SymbolTableCollection symbolTable;
1566 auto fn = cast<FunctionOpInterface>(
1567 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
1569 if (fn.getFunctionBody().empty()) {
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");
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");
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");
1594 positionSize, constraintSize);
1595 FunctionOpInterface NewF = putils->newFunc;
1597 OpBuilder entryBuilder(putils->initializationBlock,
1598 putils->initializationBlock->begin());
1599 Location initLoc = putils->initializationBlock->begin()->getLoc();
1601 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
1603 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
1604 DenseElementsAttr::get(scalarType, 0.0));
1605 Value weightAccumulator = zeroWeight;
1608 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
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;
1616 SmallVector<Operation *> toErase;
1617 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
1618 OpBuilder::InsertionGuard guard(rewriter);
1619 rewriter.setInsertionPoint(sampleOp);
1621 SmallVector<Value> sampledValues;
1622 bool isDistribution =
static_cast<bool>(sampleOp.getLogpdfAttr());
1624 if (isDistribution) {
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) {
1633 "Impulse: distribution function cannot have composite "
1634 "constrained address");
1635 return WalkResult::interrupt();
1637 isConstrained =
true;
1638 constrainedOffset = computeOffsetForSampleInSelection(
1639 CI, fn, CI.getConstrainedAddressesAttr(),
1640 sampleOp.getSymbolAttr(), symbolTable);
1645 if (isConstrained) {
1647 sampledValues.resize(sampleOp.getNumResults());
1648 sampledValues[0] = sampleOp.getOperand(0);
1650 for (
unsigned i = 1; i < sampleOp.getNumResults(); ++i) {
1652 cast<RankedTensorType>(sampleOp.getResult(i).getType());
1653 int64_t numElements = computeTensorElementCount(resultType);
1654 if (numElements < 0) {
1656 "Impulse: dynamic tensor dimensions not supported");
1657 return WalkResult::interrupt();
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;
1676 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1677 sampleOp, sampleOp.getLogpdfAttr()));
1680 SmallVector<Value> logpdfOperands;
1681 for (
unsigned i = 1; i < sampledValues.size(); ++i) {
1682 logpdfOperands.push_back(sampledValues[i]);
1684 for (
unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
1685 logpdfOperands.push_back(sampleOp.getOperand(i));
1688 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
1690 "Impulse: failed to construct logpdf call for constrained "
1691 "sample; logpdf function has wrong number of arguments");
1692 return WalkResult::interrupt();
1695 auto logpdf = func::CallOp::create(
1696 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
1697 logpdfFn.getResultTypes(), logpdfOperands);
1699 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
1700 weightAccumulator, logpdf.getResult(0));
1704 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1705 sampleOp, sampleOp.getFnAttr()));
1707 auto distCall = func::CallOp::create(
1708 rewriter, sampleOp.getLoc(), distFn.getName(),
1709 distFn.getResultTypes(), sampleOp.getInputs());
1711 sampledValues.append(distCall.getResults().begin(),
1712 distCall.getResults().end());
1715 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1716 sampleOp, sampleOp.getLogpdfAttr()));
1718 SmallVector<Value> logpdfOperands;
1719 for (
unsigned i = 1; i < sampledValues.size(); ++i) {
1720 logpdfOperands.push_back(sampledValues[i]);
1722 for (
unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
1723 logpdfOperands.push_back(sampleOp.getOperand(i));
1726 if (logpdfOperands.size() != logpdfFn.getNumArguments()) {
1728 "Impulse: failed to construct logpdf call; "
1729 "logpdf function has wrong number of arguments");
1730 return WalkResult::interrupt();
1733 auto logpdf = func::CallOp::create(
1734 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
1735 logpdfFn.getResultTypes(), logpdfOperands);
1737 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
1738 weightAccumulator, logpdf.getResult(0));
1741 bool inSelection =
false;
1742 for (
auto addr : selection) {
1743 auto address = cast<ArrayAttr>(addr);
1744 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
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) {
1757 "Impulse: dynamic tensor dimensions not supported");
1758 return WalkResult::interrupt();
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})
1777 currentTraceOffset += numElements;
1783 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
1784 sampleOp, sampleOp.getFnAttr()));
1786 if (genFn.getFunctionBody().empty()) {
1788 "Impulse: generative function body is empty; "
1789 "if this is a distribution, add a logpdf attribute");
1790 return WalkResult::interrupt();
1793 ArrayAttr subSelection =
1794 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
1795 ArrayAttr subConstrainedAddrs =
1796 buildSubSelection(rewriter, CI.getConstrainedAddressesAttr(),
1797 sampleOp.getSymbolAttr());
1799 if (subSelection.empty()) {
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());
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();
1818 Value subConstraint;
1819 auto subConstraintType = RankedTensorType::get(
1820 {1, subConstraintSize}, rewriter.getF64Type());
1822 if (subConstraintSize > 0) {
1823 int64_t subConstraintOffset = computeOffsetForNestedSample(
1824 sampleOp, fn, CI.getConstrainedAddressesAttr(),
1825 sampleOp.getSymbolAttr(), symbolTable);
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}));
1834 subConstraint = arith::ConstantOp::create(
1835 rewriter, sampleOp.getLoc(), subConstraintType,
1836 DenseElementsAttr::get(subConstraintType, {0.0}));
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);
1849 auto nestedGenerate = impulse::GenerateOp::create(
1850 rewriter, sampleOp.getLoc(), genResultTypes,
1851 sampleOp.getFnAttr(), sampleOp.getInputs(), subConstraint,
1852 subSelection, subConstrainedAddrs);
1854 Value subTrace = nestedGenerate.getTrace();
1855 Value subWeight = nestedGenerate.getWeight();
1857 weightAccumulator = arith::AddFOp::create(
1858 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
1860 int64_t mergeOffset = computeOffsetForNestedSample(
1861 sampleOp, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
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})
1875 currentTraceOffset =
1876 std::max(currentTraceOffset, mergeOffset + subPositionSize);
1878 for (
auto output : nestedGenerate.getOutputs())
1879 sampledValues.push_back(output);
1883 sampleOp.replaceAllUsesWith(sampledValues);
1884 toErase.push_back(sampleOp);
1885 return WalkResult::advance();
1888 for (Operation *op : toErase)
1889 rewriter.eraseOp(op);
1891 if (result.wasInterrupted()) {
1892 CI.emitError(
"Impulse: failed to walk sample ops");
1897 NewF.walk([&](func::ReturnOp retOp) {
1898 OpBuilder::InsertionGuard guard(rewriter);
1899 rewriter.setInsertionPoint(retOp);
1901 SmallVector<Value> newRetVals;
1902 newRetVals.push_back(currTrace);
1903 newRetVals.push_back(weightAccumulator);
1904 newRetVals.append(retOp.getOperands().begin(),
1905 retOp.getOperands().end());
1907 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
1908 rewriter.eraseOp(retOp);
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);
1918 rewriter.replaceOp(CI, newCI.getResults());
1926 struct LowerRegeneratePattern
1927 :
public mlir::OpRewritePattern<impulse::RegenerateOp> {
1930 LogicalResult matchAndRewrite(impulse::RegenerateOp CI,
1931 PatternRewriter &rewriter)
const override {
1932 SymbolTableCollection symbolTable;
1934 auto fn = cast<FunctionOpInterface>(
1935 symbolTable.lookupNearestSymbolFrom(CI, CI.getFnAttr()));
1937 if (fn.getFunctionBody().empty()) {
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");
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");
1956 FunctionOpInterface NewF = putils->newFunc;
1958 OpBuilder entryBuilder(putils->initializationBlock,
1959 putils->initializationBlock->begin());
1960 Location initLoc = putils->initializationBlock->begin()->getLoc();
1962 auto scalarType = RankedTensorType::get({}, entryBuilder.getF64Type());
1964 arith::ConstantOp::create(entryBuilder, initLoc, scalarType,
1965 DenseElementsAttr::get(scalarType, 0.0));
1966 Value weightAccumulator = zeroWeight;
1969 RankedTensorType::get({1, positionSize}, entryBuilder.getF64Type());
1971 arith::ConstantOp::create(entryBuilder, initLoc,
traceType,
1972 DenseElementsAttr::get(
traceType, 0.0));
1973 Value currTrace = zeroTrace;
1975 Value prevTrace = NewF.getArgument(0);
1976 int64_t currentTraceOffset = 0;
1978 SmallVector<Operation *> toErase;
1979 auto result = NewF.walk([&](impulse::SampleOp sampleOp) -> WalkResult {
1980 OpBuilder::InsertionGuard guard(rewriter);
1981 rewriter.setInsertionPoint(sampleOp);
1983 SmallVector<Value> sampledValues;
1984 bool isDistribution =
static_cast<bool>(sampleOp.getLogpdfAttr());
1986 if (isDistribution) {
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) {
1994 "Impulse: distribution function cannot have composite "
1995 "selected address");
1996 return WalkResult::interrupt();
2003 int64_t sampleOffset = computeOffsetForSampleInSelection(
2004 CI, fn, selection, sampleOp.getSymbolAttr(), symbolTable);
2009 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2010 sampleOp, sampleOp.getFnAttr()));
2012 auto distCall = func::CallOp::create(
2013 rewriter, sampleOp.getLoc(), distFn.getName(),
2014 distFn.getResultTypes(), sampleOp.getInputs());
2016 sampledValues.append(distCall.getResults().begin(),
2017 distCall.getResults().end());
2020 sampledValues.resize(sampleOp.getNumResults());
2021 sampledValues[0] = sampleOp.getOperand(0);
2023 int64_t extractOffset = sampleOffset;
2024 for (
unsigned i = 1; i < sampleOp.getNumResults(); ++i) {
2026 cast<RankedTensorType>(sampleOp.getResult(i).getType());
2027 int64_t numElements = computeTensorElementCount(resultType);
2028 if (numElements < 0) {
2030 "Impulse: dynamic tensor dimensions not supported");
2031 return WalkResult::interrupt();
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;
2050 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2051 sampleOp, sampleOp.getLogpdfAttr()));
2053 SmallVector<Value> logpdfOperands;
2054 for (
unsigned i = 1; i < sampledValues.size(); ++i) {
2055 logpdfOperands.push_back(sampledValues[i]);
2057 for (
unsigned i = 1; i < sampleOp.getNumOperands(); ++i) {
2058 logpdfOperands.push_back(sampleOp.getOperand(i));
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();
2067 auto logpdf = func::CallOp::create(
2068 rewriter, sampleOp.getLoc(), logpdfFn.getName(),
2069 logpdfFn.getResultTypes(), logpdfOperands);
2071 arith::AddFOp::create(rewriter, sampleOp.getLoc(),
2072 weightAccumulator, logpdf.getResult(0));
2074 bool inSelection =
false;
2075 for (
auto addr : selection) {
2076 auto address = cast<ArrayAttr>(addr);
2077 if (!address.empty() && address[0] == sampleOp.getSymbolAttr()) {
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) {
2090 "Impulse: dynamic tensor dimensions not supported");
2091 return WalkResult::interrupt();
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})
2110 currentTraceOffset += numElements;
2116 cast<FunctionOpInterface>(symbolTable.lookupNearestSymbolFrom(
2117 sampleOp, sampleOp.getFnAttr()));
2119 if (genFn.getFunctionBody().empty()) {
2121 "Impulse: generative function body is empty; "
2122 "if this is a distribution, add a logpdf attribute");
2123 return WalkResult::interrupt();
2126 ArrayAttr subSelection =
2127 buildSubSelection(rewriter, selection, sampleOp.getSymbolAttr());
2128 ArrayAttr subRegenerateAddrs =
2129 buildSubSelection(rewriter, CI.getRegenerateAddressesAttr(),
2130 sampleOp.getSymbolAttr());
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());
2139 int64_t subPositionSize = computePositionSizeForSelection(
2140 sampleOp, genFn, subSelection, symbolTable);
2141 if (subPositionSize <= 0) {
2143 "Impulse: failed to compute sub-position size");
2144 return WalkResult::interrupt();
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();
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}));
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);
2171 auto nestedRegenerate = impulse::RegenerateOp::create(
2172 rewriter, sampleOp.getLoc(), regenResultTypes,
2173 sampleOp.getFnAttr(), sampleOp.getInputs(), subPrevTrace,
2174 subSelection, subRegenerateAddrs);
2176 Value subTrace = nestedRegenerate.getNewTrace();
2177 Value subWeight = nestedRegenerate.getWeight();
2179 weightAccumulator = arith::AddFOp::create(
2180 rewriter, sampleOp.getLoc(), weightAccumulator, subWeight);
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})
2194 currentTraceOffset =
2195 std::max(currentTraceOffset, mergeOffset + subPositionSize);
2197 for (
auto output : nestedRegenerate.getOutputs())
2198 sampledValues.push_back(output);
2202 sampleOp.replaceAllUsesWith(sampledValues);
2203 toErase.push_back(sampleOp);
2204 return WalkResult::advance();
2207 for (Operation *op : toErase)
2208 rewriter.eraseOp(op);
2210 if (result.wasInterrupted()) {
2211 CI.emitError(
"Impulse: failed to walk sample ops");
2215 NewF.walk([&](func::ReturnOp retOp) {
2216 OpBuilder::InsertionGuard guard(rewriter);
2217 rewriter.setInsertionPoint(retOp);
2219 SmallVector<Value> newRetVals;
2220 newRetVals.push_back(currTrace);
2221 newRetVals.push_back(weightAccumulator);
2222 newRetVals.append(retOp.getOperands().begin(),
2223 retOp.getOperands().end());
2225 func::ReturnOp::create(rewriter, retOp.getLoc(), newRetVals);
2226 rewriter.eraseOp(retOp);
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);
2236 rewriter.replaceOp(CI, newCI.getResults());
2247void ExpandImpulsePass::runOnOperation() {
2248 RewritePatternSet patterns(&getContext());
2249 patterns.add<LowerUntracedCallPattern, LowerSimulatePattern,
2250 LowerGeneratePattern, LowerMHPattern, LowerRegeneratePattern>(
2252 patterns.add<LowerMCMCPattern>(&getContext(), debugDump);
2254 mlir::GreedyRewriteConfig config;
2257 applyPatternsGreedily(getOperation(), std::move(patterns), config))) {
2258 signalPassFailure();
2262 if (!postpasses.empty()) {
2263 mlir::PassManager pm(getOperation()->getContext());
2265 if (mlir::failed(mlir::parsePassPipeline(postpasses, pm))) {
2266 getOperation()->emitError()
2267 <<
"Failed to parse expand-impulse post-passes pipeline: "
2269 signalPassFailure();
2273 if (mlir::failed(pm.run(getOperation()))) {
2274 signalPassFailure();
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.
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:
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.
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.
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
static DualAveragingState fromValues(ArrayRef< Value > values)
SmallVector< Value > toValues() const
Result of one MCMC kernel step.
Configuration for Welford covariance estimation.
State for Welford covariance estimation.
SmallVector< Value > toValues() const
SmallVector< Type > getTypes() const
static WelfordState fromValues(ArrayRef< Value > values)