Skip to content

Commit eecd7e7

Browse files
committed
fp: exploit the declared fast relaxation with one horizontal schedule
Every f32 call site that writes contract=fast has, until now, been declaring a relaxation nothing exploited: the lowering kept the same ordered fused chain the fma contract pins. The new vectorize-ondsp-fp-fast-memref-reduce pass is that relaxation's one consumer. It splits a rank-1 fast reduction into eight-lane fused partial sums, one cross-lane vector reduction, and a scalar tail, with every floating-point operation carrying exactly the declared fastmath<reassoc,contract> profile. off and fma are refused - the exact inverse of the order-preserving batching pass, whose lanes may never regroup one output's chain. The rewrite reorders reads only and produces one scalar, so unlike both batching passes it carries no aliasing obligation. It runs inside the canonical schedule stage: the per-call-site contract is the opt-in, and the standalone flag remains the ablation interface. One MLIR 17 fact is recorded at the emission site: vector.reduction has no fastmath attribute at this version, so the cross-lane fold is the ordered reduce.fadd intrinsic - a fixed deterministic regrouping the relaxation authorizes. A relaxed result is never bit-pinned, so the object gate carries a different evidence shape than every exact-contract gate before it: an error envelope against an f64 reference (4*N*2^-24 * sum of absolute products), repeated-call determinism, a structural pin that the vector form was emitted before any object is built, and two mechanism-directed executed divergences from the ordered fused chain - an unfused-product rounding tie the vector body resolves down and the ordered chain rounds up, and a cross-lane cancellation the ordered chain absorbs - each with its observed bit patterns recorded and each verified live by mutation. A naive period-aligned cancellation corpus does NOT diverge under the ordered cross-lane fold; a divergence corpus counts as a witness only once it is executed and observed to diverge.
1 parent 63ad96e commit eecd7e7

10 files changed

Lines changed: 514 additions & 1 deletion

File tree

docs/status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ the numeric model is stabilized.
1212
| `ondrix`, `ondsp`, and `ortumcore` dialects | Implemented; contracts remain experimental |
1313
| Typed conversion patterns and accumulator type conversion | Implemented |
1414
| Generic scalar lowering | Signed-Q15 full product with signed frac30 accumulator widths of at least 32 bits and signed-Q31 full/raw-high accumulator operations implemented; ordered rank-1 Q15/Q31 memref reductions implemented; from a supported signed frac30 accumulator the export boundary lowers a signed Q15 destination, any signed i32 destination fraction the verifier admits, and the identity signed i64/frac30 reading, while the i64/frac62 profile remains restricted to signed Q31 export; every export is a value-preserving format conversion, and value-changing scalings belong to the declared arithmetic `round_shift`; rank-1 f32 reduction is partial |
15-
| Floating-point contract policy | Every f32 call site names one of three declared evaluation policies, and the policy indexes the correctness relation its evidence must establish: `off` keeps separate ordered IEEE multiplies and adds (no fast-math flags, not even contraction) and `fma` pins explicit single-rounding fused multiply-add events (`math.fma`/`llvm.fma`) — both are exact contracts gated bit-for-bit against independent references; `fast` is a named relaxation profile declaring exactly reassociation plus multiply-add contraction (`fastmath<reassoc,contract>`, never LLVM's blanket `fast`, which would additionally assert NaN/Inf absence, abandon signed-zero observability, and license reciprocal division and approximate functions the language never declared), so `fast` results are characterized under an explicit error envelope and never bit-pinned; all three modes share the same environment contract (round-to-nearest-even, unobservable exception state, subnormals preserved) and the relaxation replaces only the bit-equivalence obligation — memory, bounds, and every structural legality obligation remain discharged by analysis |
15+
| Floating-point contract policy | Every f32 call site names one of three declared evaluation policies, and the policy indexes the correctness relation its evidence must establish: `off` keeps separate ordered IEEE multiplies and adds (no fast-math flags, not even contraction) and `fma` pins explicit single-rounding fused multiply-add events (`math.fma`/`llvm.fma`) — both are exact contracts gated bit-for-bit against independent references; `fast` is a named relaxation profile declaring exactly reassociation plus multiply-add contraction (`fastmath<reassoc,contract>`, never LLVM's blanket `fast`, which would additionally assert NaN/Inf absence, abandon signed-zero observability, and license reciprocal division and approximate functions the language never declared), so `fast` results are characterized under an explicit error envelope and never bit-pinned; all three modes share the same environment contract (round-to-nearest-even, unobservable exception state, subnormals preserved) and the relaxation replaces only the bit-equivalence obligation — memory, bounds, and every structural legality obligation remain discharged by analysis; the declared relaxation has its one exploiting schedule, `vectorize-ondsp-fp-fast-memref-reduce` (run inside the canonical schedule stage): fused partial-sum lanes plus one cross-lane reduction over a rank-1 fast reduction, refused for off/fma, no aliasing obligation (reads only), and gated by an error envelope against a double reference, a repeated-call determinism check, an executed bitwise divergence from the ordered fused chain on a directed cancellation input, and structural pins — never a bit-exact gate |
1616
| Canonical pipeline and automatic scheduling | `-ondrix-default-pipeline` (and `ondrix-compile --emit=llvm` from `.ox` source) runs the whole flow with one flag: compile-time design evaluation (the fail-closed quantization tie guard aborts the compile rather than ship a misquantized table), contract conversion (operations whose reductions bufferize directly stay in contract form), proof-gated constant-index forwarding before bufferization (`forward-ondrix-insert-extract`, described under Spectral magnitude), boundary bufferization, an automatic schedule stage, and lowering to the LLVM dialect; inside the schedule stage every candidate transform is filtered by its own legality analysis and applied in a documented priority order (order-preserving vertical batchings first, then saturating-certified and wrap horizontal reductions on the sites batching cannot serve), so an unauthorized site keeps its ordered scalar schedule and the user's only inputs are the numeric contracts in the source; the target vector width is a pipeline parameter in register bits from which each transform's lane count is derived (the same module compiles at any width; zero yields the all-ordered program); the individual pass flags remain available for ablation, testing, and oracle runs; cost selection is the fixed priority order plus each pass's own profitability guards — a measured regret evaluation against the best legal candidate is planned for the frozen-revision evaluation; the composed four-stage `.ox` program (windowed-sinc design, valid FIR, RFFT64, magnitude) compiles through this pipeline with no hand-picked passes — the evaluated design constants feed the certified saturating horizontal reduction — and its default-width and ordered width-zero objects are both gated bit-exactly against one independent C reference, so schedule invariance of the exact contracts is a checked fact |
1717
| Generic Vector CPU lowering | Automatic unit-stride Q15/Q31 chunking, ordered saturating updates, and exact-modulo wrapping reduction implemented; the horizontal wrapping path admits signed frac30 Q15 accumulators wider than i40 up to i64, since exact-modulo reassociation legality is width-independent up to the i64 lane carrier; a genuinely wrapping i48 reduction object gate pins horizontal, ordered, and an exact mod-2^48 reference bit-identical on cases whose ordered prefixes leave the accumulator range, including an exact full-turn return to zero |
1818
| Algorithm transforms | Opt-in specialization for static rank-1 FIR samples using immutable constant memref globals or proven complete unit-stride views: zero-tap elimination, exact-modulo symmetric full-product pairing, and symmetric saturating pairing when complete prefix-range analysis proves both schedules safe |

include/ondrix/Conversion/Passes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ std::unique_ptr<mlir::Pass> createVectorizeOndspFixedDecimateOutputsPass(
3131
std::unique_ptr<mlir::Pass> createVectorizeOndspFpFilterOutputsPass();
3232
std::unique_ptr<mlir::Pass>
3333
createVectorizeOndspFpFilterOutputsPass(const VectorizeOndspFpFilterOutputsOptions &options);
34+
std::unique_ptr<mlir::Pass> createVectorizeOndspFpFastMemRefReducePass();
35+
std::unique_ptr<mlir::Pass>
36+
createVectorizeOndspFpFastMemRefReducePass(const VectorizeOndspFpFastMemRefReduceOptions &options);
3437

3538
#define GEN_PASS_REGISTRATION
3639
#include "ondrix/Conversion/Passes.h.inc"

include/ondrix/Conversion/Passes.td

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,47 @@ def VectorizeOndspFixedDecimateOutputs
397397
];
398398
}
399399

400+
def VectorizeOndspFpFastMemRefReduce
401+
: Pass<"vectorize-ondsp-fp-fast-memref-reduce", "mlir::ModuleOp"> {
402+
let summary = "Vectorize f32 reductions whose call sites declared the fast "
403+
"relaxation";
404+
let description = [{
405+
Splits a rank-1 f32 `ondsp.reduce_mac` under `contract = fast` into
406+
W-lane fused partial sums, one cross-lane `vector.reduction <add>`, and a
407+
scalar tail, every floating-point operation carrying exactly the declared
408+
`fastmath<reassoc, contract>` profile. This is the one transform that
409+
exploits the relaxation the fast contract declares: regrouping one
410+
output's accumulation chain across lanes is precisely the reassociation a
411+
call site authorizes by writing `contract = fast`, and precisely the
412+
value change the off and fma contracts forbid, so those two are refused
413+
here — the exact inverse of the order-preserving batching pass's refusal
414+
of fast. The rewrite reorders reads only and produces one scalar, so
415+
unlike the batching passes it carries no aliasing obligation.
416+
417+
Because the result is declaratively relaxed, its evidence is an error
418+
envelope against a higher-precision reference, a determinism check, and a
419+
structural pin of the emitted vector form — never a bit-exact gate. The
420+
canonical pipeline runs this pass inside its schedule stage; the
421+
standalone flag remains an ablation and oracle interface. Only the
422+
default LLVM memory space and statically unit-strided minor dimensions
423+
are supported; other reductions remain unchanged for scalar lowering.
424+
}];
425+
let constructor = "ondrix::createVectorizeOndspFpFastMemRefReducePass()";
426+
let dependentDialects = [
427+
"ondrix::ondsp::OndspDialect",
428+
"mlir::arith::ArithDialect",
429+
"mlir::cf::ControlFlowDialect",
430+
"mlir::math::MathDialect",
431+
"mlir::memref::MemRefDialect",
432+
"mlir::scf::SCFDialect",
433+
"mlir::vector::VectorDialect",
434+
];
435+
let options = [
436+
Option<"vectorWidth", "vector-width", "int64_t", /*default=*/"8",
437+
"Number of f32 lanes per partial-sum vector">,
438+
];
439+
}
440+
400441
def VectorizeOndspFpFilterOutputs
401442
: Pass<"vectorize-ondsp-fp-filter-outputs", "mlir::ModuleOp"> {
402443
let summary = "Batch independent f32 filter outputs into vector lanes under "

lib/Conversion/OndspVectorization/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_ondrix_library(OndspVectorization
22
OndspDecimateOutputBatching.cpp
3+
OndspFpFastMemRefReduce.cpp
34
OndspFpFilterOutputBatching.cpp
45
OndspVectorization.cpp
56

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#include "ondrix/Conversion/OndspVectorization/OndspVectorization.h"
2+
#include "ondrix/Conversion/Utils/MemRefLayoutUtils.h"
3+
#include "ondrix/Conversion/Utils/ReductionUtils.h"
4+
5+
#include "ondrix/Dialect/ondsp/IR/OndspDialect.h"
6+
#include "ondrix/Dialect/ondsp/IR/OndspOps.h"
7+
#include "ondrix/Dialect/ondsp/IR/OndspSemantics.h"
8+
9+
#include "mlir/Dialect/Arith/IR/Arith.h"
10+
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
11+
#include "mlir/Dialect/Math/IR/Math.h"
12+
#include "mlir/Dialect/MemRef/IR/MemRef.h"
13+
#include "mlir/Dialect/SCF/IR/SCF.h"
14+
#include "mlir/Dialect/Vector/IR/VectorOps.h"
15+
#include "mlir/IR/BuiltinOps.h"
16+
#include "mlir/Pass/Pass.h"
17+
#include "mlir/Transforms/DialectConversion.h"
18+
19+
namespace ondrix {
20+
#define GEN_PASS_DEF_VECTORIZEONDSPFPFASTMEMREFREDUCE
21+
#include "ondrix/Conversion/Passes.h.inc"
22+
} // namespace ondrix
23+
24+
using namespace mlir;
25+
26+
namespace {
27+
28+
/// Largest accepted lane count, bounding every index the rewrite derives.
29+
constexpr int64_t kMaxVectorWidth = 4096;
30+
31+
bool isSupportedFastMemRefReduction(ondrix::ondsp::ReduceMacOp op) {
32+
auto numeric = dyn_cast<ondrix::ondsp::FpAttr>(op.getNumeric());
33+
if (!numeric || !numeric.getFormat().isF32() ||
34+
numeric.getContract() != ondrix::ondsp::FpContractMode::Fast || op.getProduct() ||
35+
!op.getInitial().getType().isF32())
36+
return false;
37+
auto lhsType = dyn_cast<MemRefType>(op.getLhs().getType());
38+
auto rhsType = dyn_cast<MemRefType>(op.getRhs().getType());
39+
return lhsType && rhsType && lhsType.getRank() == 1 && rhsType.getRank() == 1 &&
40+
lhsType.getElementType().isF32() && rhsType.getElementType().isF32() &&
41+
ondrix::conversion::hasDefaultLLVMVectorMemorySpace(lhsType) &&
42+
ondrix::conversion::hasDefaultLLVMVectorMemorySpace(rhsType) &&
43+
isLastMemrefDimUnitStride(lhsType) && isLastMemrefDimUnitStride(rhsType);
44+
}
45+
46+
class FastReduceMacOpVectorization final : public OpConversionPattern<ondrix::ondsp::ReduceMacOp> {
47+
public:
48+
FastReduceMacOpVectorization(MLIRContext *context, int64_t vectorWidth)
49+
: OpConversionPattern(context), vectorWidth(vectorWidth) {}
50+
51+
LogicalResult matchAndRewrite(ondrix::ondsp::ReduceMacOp op, OpAdaptor adaptor,
52+
ConversionPatternRewriter &rewriter) const override {
53+
if (!isSupportedFastMemRefReduction(op))
54+
return failure();
55+
56+
Type elementType = rewriter.getF32Type();
57+
FailureOr<ondrix::conversion::RankOneReductionBounds> bounds =
58+
ondrix::conversion::createRankOneMemRefReductionBounds(
59+
op, adaptor.getLhs(), adaptor.getRhs(), elementType, "fast f32 memref vectorization",
60+
rewriter);
61+
if (failed(bounds))
62+
return failure();
63+
64+
Location loc = op.getLoc();
65+
auto fastFlags =
66+
arith::FastMathFlagsAttr::get(getContext(), ondrix::ondsp::getFastContractFlags());
67+
Value vectorStep = rewriter.create<arith::ConstantIndexOp>(loc, vectorWidth);
68+
Value remainder = rewriter.create<arith::RemUIOp>(loc, bounds->upperBound, vectorStep);
69+
Value vectorEnd = rewriter.create<arith::SubIOp>(loc, bounds->upperBound, remainder);
70+
auto vectorType = VectorType::get({vectorWidth}, elementType);
71+
Value zeroLanes = rewriter.create<arith::ConstantOp>(
72+
loc, vectorType, DenseElementsAttr::get(vectorType, rewriter.getF32FloatAttr(0.0f)));
73+
74+
auto vectorLoop = rewriter.create<scf::ForOp>(
75+
loc, bounds->lowerBound, vectorEnd, vectorStep, ValueRange{zeroLanes},
76+
[&](OpBuilder &builder, Location bodyLoc, Value base, ValueRange iterArgs) {
77+
Value lhs = builder.create<vector::LoadOp>(bodyLoc, vectorType, adaptor.getLhs(), base);
78+
Value rhs = builder.create<vector::LoadOp>(bodyLoc, vectorType, adaptor.getRhs(), base);
79+
Value next = builder.create<math::FmaOp>(bodyLoc, lhs, rhs, iterArgs.front(), fastFlags);
80+
builder.create<scf::YieldOp>(bodyLoc, next);
81+
});
82+
83+
// MLIR 17's vector.reduction carries no fastmath attribute; it lowers to
84+
// the ordered llvm.intr.vector.reduce.fadd, a fixed deterministic
85+
// regrouping the declared relaxation authorizes.
86+
Value reduced = rewriter.create<vector::ReductionOp>(loc, vector::CombiningKind::ADD,
87+
vectorLoop.getResult(0));
88+
89+
Value scalarStep = rewriter.create<arith::ConstantIndexOp>(loc, 1);
90+
auto tailLoop = rewriter.create<scf::ForOp>(
91+
loc, vectorEnd, bounds->upperBound, scalarStep, ValueRange{reduced},
92+
[&](OpBuilder &builder, Location bodyLoc, Value index, ValueRange iterArgs) {
93+
Value lhs = builder.create<memref::LoadOp>(bodyLoc, adaptor.getLhs(), index);
94+
Value rhs = builder.create<memref::LoadOp>(bodyLoc, adaptor.getRhs(), index);
95+
Value next = builder.create<math::FmaOp>(bodyLoc, lhs, rhs, iterArgs.front(), fastFlags);
96+
builder.create<scf::YieldOp>(bodyLoc, next);
97+
});
98+
99+
rewriter.replaceOpWithNewOp<arith::AddFOp>(op, tailLoop.getResult(0), adaptor.getInitial(),
100+
fastFlags);
101+
return success();
102+
}
103+
104+
private:
105+
int64_t vectorWidth;
106+
};
107+
108+
class VectorizeOndspFpFastMemRefReducePass final
109+
: public ondrix::impl::VectorizeOndspFpFastMemRefReduceBase<
110+
VectorizeOndspFpFastMemRefReducePass> {
111+
public:
112+
using ondrix::impl::VectorizeOndspFpFastMemRefReduceBase<
113+
VectorizeOndspFpFastMemRefReducePass>::VectorizeOndspFpFastMemRefReduceBase;
114+
115+
void runOnOperation() override {
116+
if (vectorWidth <= 1) {
117+
getOperation().emitError("vector-width must be greater than one");
118+
signalPassFailure();
119+
return;
120+
}
121+
if (vectorWidth > kMaxVectorWidth) {
122+
getOperation().emitError("vector-width must not exceed ") << kMaxVectorWidth;
123+
signalPassFailure();
124+
return;
125+
}
126+
127+
RewritePatternSet patterns(&getContext());
128+
patterns.add<FastReduceMacOpVectorization>(&getContext(), vectorWidth);
129+
130+
ConversionTarget target(getContext());
131+
target.addLegalDialect<arith::ArithDialect, cf::ControlFlowDialect, math::MathDialect,
132+
memref::MemRefDialect, ondrix::ondsp::OndspDialect, scf::SCFDialect,
133+
vector::VectorDialect>();
134+
target.addDynamicallyLegalOp<ondrix::ondsp::ReduceMacOp>(
135+
[](ondrix::ondsp::ReduceMacOp op) { return !isSupportedFastMemRefReduction(op); });
136+
137+
if (failed(applyPartialConversion(getOperation(), target, std::move(patterns))))
138+
signalPassFailure();
139+
}
140+
};
141+
142+
} // namespace
143+
144+
std::unique_ptr<Pass> ondrix::createVectorizeOndspFpFastMemRefReducePass() {
145+
return std::make_unique<VectorizeOndspFpFastMemRefReducePass>();
146+
}
147+
148+
std::unique_ptr<Pass> ondrix::createVectorizeOndspFpFastMemRefReducePass(
149+
const ondrix::VectorizeOndspFpFastMemRefReduceOptions &options) {
150+
return std::make_unique<VectorizeOndspFpFastMemRefReducePass>(options);
151+
}

lib/Pipelines/OndrixPipelines.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ std::string defaultPipelineText(const ondrix::OndrixDefaultPipelineOptions &opti
4747
if (options.vectorBits >= 64) {
4848
int64_t lanes = options.vectorBits / 32;
4949
os << llvm::formatv("vectorize-ondsp-fp-filter-outputs{{vector-width={0}},", lanes);
50+
os << llvm::formatv("vectorize-ondsp-fp-fast-memref-reduce{{vector-width={0}},", lanes);
5051
os << llvm::formatv("vectorize-ondsp-fixed-decimate-outputs{{vector-width={0}},", lanes);
5152
os << llvm::formatv(
5253
"vectorize-ondsp-constant-saturating-memref-reduce{{vector-width={0} max-elements=64},",

0 commit comments

Comments
 (0)