@@ -3216,12 +3216,136 @@ class DecomposeAtenLogCumsumExpOp
32163216 if (!isValidDim(dim, inputRank))
32173217 return rewriter.notifyMatchFailure(op, "invalid dim.");
32183218
3219- Value dtypeVal =
3220- getDtypeIntValueForType(rewriter, loc, inputType.getDtype());
3221- Value expInput = AtenExpOp::create(rewriter, loc, resultType, input);
3222- Value cumsum = AtenCumsumOp::create(rewriter, loc, resultType, expInput,
3223- op.getDim(), dtypeVal);
3224- rewriter.replaceOpWithNewOp<AtenLogOp>(op, resultType, cumsum);
3219+ // logcumsumexp(x)[j] = log(sum_{i<=j} exp(x[i])) is an inclusive prefix
3220+ // scan whose combiner is logaddexp (associative, commutative, with identity
3221+ // -inf):
3222+ // out[j] = logaddexp_{i<=j} x[i], out[j] = logaddexp(out[j-1], x[j]).
3223+ // Each per-step logaddexp is emitted as aten.logaddexp and stabilized by
3224+ // DecomposeAtenLogAddExpOp to max(a,b) + log1p(exp(-|a-b|)) in this same
3225+ // pass, so the only exp taken is exp(-|a-b|) in [0,1] -- the shift is
3226+ // prefix-local, never a single global max over the whole scan. That
3227+ // locality avoids both the +inf overflow of naive log(cumsum(exp(x))) and
3228+ // the -inf underflow of a global-max shift M + log(cumsum(exp(x - M)))
3229+ // when a large value sits late in the scan.
3230+ Value dimValue =
3231+ ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(dim));
3232+ Value cstOne =
3233+ ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(1));
3234+ Value cstNone = ConstantNoneOp::create(rewriter, loc);
3235+
3236+ ArrayRef<int64_t> inSizes = inputType.getSizes();
3237+ int64_t staticDimSize = inSizes[dim];
3238+
3239+ // Static scan length: emit a compile-time-unrolled Hillis-Steele inclusive
3240+ // scan (ceil(log2(L)) doubling steps). Each step shifts the running result
3241+ // right by `offset` along `dim` -- padding the low side with the -inf
3242+ // identity via aten.constant_pad_nd, then slicing back to length L -- and
3243+ // folds it in with an elementwise aten.logaddexp:
3244+ // running[k] = logaddexp(running[k], running[k - offset]).
3245+ // This uses only pad/slice/logaddexp, which legalize to linalg, TOSA and
3246+ // StableHLO alike (matching how aten.cumsum is lowered on those backends,
3247+ // which likewise require a static scan dim). logaddexp(a, -inf) == a, so
3248+ // the padded low end is inert.
3249+ if (staticDimSize != kUnknownSize) {
3250+ Value running = AtenCloneOp::create(rewriter, loc, resultType, input,
3251+ /*memory_format=*/cstNone);
3252+ Value negInf = ConstantFloatOp::create(
3253+ rewriter, loc,
3254+ rewriter.getF64FloatAttr(-std::numeric_limits<double>::infinity()));
3255+ Value cstZero =
3256+ ConstantIntOp::create(rewriter, loc, rewriter.getI64IntegerAttr(0));
3257+ Value cstDimSize = ConstantIntOp::create(
3258+ rewriter, loc, rewriter.getI64IntegerAttr(staticDimSize));
3259+ Type intListType =
3260+ Torch::ListType::get(Torch::IntType::get(op.getContext()));
3261+
3262+ for (int64_t offset = 1; offset < staticDimSize; offset <<= 1) {
3263+ // aten.constant_pad_nd pad list is ordered last-dim-first as
3264+ // [lastdim_lo, lastdim_hi, ...]; pad `offset` on the low side of `dim`.
3265+ SmallVector<int64_t> padAmts(2 * inputRank, 0);
3266+ padAmts[2 * (inputRank - 1 - dim)] = offset;
3267+ SmallVector<Value> padVals;
3268+ for (int64_t p : padAmts)
3269+ padVals.push_back(ConstantIntOp::create(
3270+ rewriter, loc, rewriter.getI64IntegerAttr(p)));
3271+ Value padList =
3272+ PrimListConstructOp::create(rewriter, loc, intListType, padVals);
3273+
3274+ SmallVector<int64_t> paddedSizes(inSizes.begin(), inSizes.end());
3275+ paddedSizes[dim] = staticDimSize + offset;
3276+ Type paddedType = inputType.getWithSizesAndDtype(
3277+ paddedSizes, inputType.getOptionalDtype());
3278+ Value padded = AtenConstantPadNdOp::create(rewriter, loc, paddedType,
3279+ running, padList, negInf);
3280+
3281+ // shifted = padded[..., 0:L, ...] -- `running` shifted right by
3282+ // `offset` along `dim`, low end filled with the -inf identity.
3283+ Value shifted = AtenSliceTensorOp::create(
3284+ rewriter, loc, resultType, padded, dimValue, /*start=*/cstZero,
3285+ /*end=*/cstDimSize, /*step=*/cstOne);
3286+
3287+ running = AtenLogaddexpOp::create(rewriter, loc, resultType, running,
3288+ shifted);
3289+ }
3290+ rewriter.replaceOp(op, running);
3291+ return success();
3292+ }
3293+
3294+ // Dynamic scan length: the doubling count is not known at compile time, so
3295+ // materialize the recurrence as a data-dependent torch.prim.Loop (lowers to
3296+ // scf; linalg only -- TOSA/StableHLO cannot express a dynamic-shape scan,
3297+ // same as aten.cumsum).
3298+ // out[0] = x[0]; out[j] = logaddexp(out[j-1], x[j]) for j >= 1.
3299+ Value loopCondTrue = ConstantBoolOp::create(rewriter, loc, true);
3300+
3301+ // Size-1 slice type along `dim` (dynamic, since indices are runtime ints).
3302+ SmallVector<int64_t> sliceSizes(inputType.getSizes());
3303+ sliceSizes[dim] = kUnknownSize;
3304+ Type sliceType = inputType.getWithSizesAndDtype(
3305+ sliceSizes, inputType.getOptionalDtype());
3306+
3307+ // dimSize = x.size(dim); iterate j = 1 .. dimSize-1 (trip = dimSize - 1).
3308+ Value dimSize = AtenSizeIntOp::create(rewriter, loc, input, dimValue);
3309+ Value trip = AtenSubIntOp::create(rewriter, loc, dimSize, cstOne);
3310+
3311+ // Seed out = clone(x): out[...,0] already equals logcumsumexp(x)[...,0].
3312+ Value initOut = AtenCloneOp::create(rewriter, loc, resultType, input,
3313+ /*memory_format=*/cstNone);
3314+
3315+ auto scanLoop =
3316+ PrimLoopOp::create(rewriter, loc, TypeRange({resultType}), trip,
3317+ loopCondTrue, ValueRange({initOut}));
3318+ {
3319+ PatternRewriter::InsertionGuard guard(rewriter);
3320+ Type loopIndexType = rewriter.getType<IntType>();
3321+ Block *body = rewriter.createBlock(
3322+ &scanLoop.getRegion(), scanLoop.getRegion().begin(),
3323+ TypeRange({loopIndexType, resultType}), {loc, loc});
3324+ Value iv = body->getArgument(0); // 0 .. dimSize-2
3325+ Value acc = body->getArgument(1); // running output
3326+ Value j =
3327+ AtenAddIntOp::create(rewriter, loc, iv, cstOne); // current index
3328+ Value jp1 = AtenAddIntOp::create(rewriter, loc, j, cstOne);
3329+
3330+ // prev = acc[..., j-1] = acc[..., iv]; cur = x[..., j].
3331+ Value prev = AtenSliceTensorOp::create(rewriter, loc, sliceType, acc,
3332+ dimValue, /*start=*/iv,
3333+ /*end=*/j, /*step=*/cstOne);
3334+ Value cur = AtenSliceTensorOp::create(rewriter, loc, sliceType, input,
3335+ dimValue, /*start=*/j,
3336+ /*end=*/jp1, /*step=*/cstOne);
3337+
3338+ // val = logaddexp(prev, cur) (stabilized by DecomposeAtenLogAddExpOp).
3339+ Value val = AtenLogaddexpOp::create(rewriter, loc, sliceType, prev, cur);
3340+
3341+ // acc[..., j] = val.
3342+ Value acc2 = AtenSliceScatterOp::create(rewriter, loc, resultType, acc,
3343+ val, dimValue, /*start=*/j,
3344+ /*end=*/jp1, /*step=*/cstOne);
3345+ PrimLoopConditionOp::create(rewriter, loc, loopCondTrue,
3346+ ValueRange({acc2}));
3347+ }
3348+ rewriter.replaceOp(op, scanLoop.getResults());
32253349 return success();
32263350 }
32273351};
0 commit comments