Skip to content

Commit ec6e187

Browse files
committed
Raise nested reductions and lower tensor products to cuTensorNet
1 parent ccaf958 commit ec6e187

14 files changed

Lines changed: 862 additions & 218 deletions

generic_solver/kernel_library_phase2.mlir

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,18 @@ module {
14861486
kernel.yield %C : tensor<?x2xf32>
14871487
}
14881488

1489+
// Separable 3D tensor product: ai,bj,ck,ijk->abc. The operands are the
1490+
// rank-6 broadcast/submap views produced by raising; ABI lowering unwraps
1491+
// them to the shared psi buffer, the u buffer, and the output buffer.
1492+
kernel.defn @cutensornetTensorProduct3D_f32_tensor(
1493+
%psiA: tensor<?x?x?x?x?x?xf32>,
1494+
%psiB: tensor<?x?x?x?x?x?xf32>,
1495+
%psiC: tensor<?x?x?x?x?x?xf32>,
1496+
%u: tensor<?x?x?x?x?x?xf32>,
1497+
%out: tensor<?x?x?x?x?x?xf32>) -> tensor<?x?x?x?x?x?xf32> {
1498+
kernel.yield %out : tensor<?x?x?x?x?x?xf32>
1499+
}
1500+
14891501
kernel.defn @cudnnConvolution2D_9tap_f16(
14901502
%A0: memref<?x?xf16, strided<[?, 1], offset: ?>>,
14911503
%A1: memref<?x?xf16, strided<[?, 1], offset: ?>>,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <math.h>
2+
#include <stdint.h>
3+
#include <stdio.h>
4+
5+
#define KP 4
6+
#define KQ 5
7+
8+
// Bare-pointer memref ABI for three memref<?xf32> arguments.
9+
extern void tensor_product_3d(
10+
float *, float *, int64_t, int64_t, int64_t,
11+
float *, float *, int64_t, int64_t, int64_t,
12+
float *, float *, int64_t, int64_t, int64_t);
13+
14+
int main(void) {
15+
float psi[KQ * KP], u[KP * KP * KP], out[KQ * KQ * KQ];
16+
for (int i = 0; i < KQ * KP; ++i)
17+
psi[i] = (float)(i - 7) / 13.0f;
18+
for (int i = 0; i < KP * KP * KP; ++i)
19+
u[i] = (float)(i % 11 - 5) / 9.0f;
20+
21+
tensor_product_3d(
22+
psi, psi, 0, KQ * KP, 1,
23+
u, u, 0, KP * KP * KP, 1,
24+
out, out, 0, KQ * KQ * KQ, 1);
25+
26+
float maxErr = 0.0f;
27+
for (int a = 0; a < KQ; ++a)
28+
for (int b = 0; b < KQ; ++b)
29+
for (int c = 0; c < KQ; ++c) {
30+
float ref = 0.0f;
31+
for (int i = 0; i < KP; ++i)
32+
for (int j = 0; j < KP; ++j)
33+
for (int k = 0; k < KP; ++k)
34+
ref += psi[a * KP + i] * psi[b * KP + j] *
35+
psi[c * KP + k] * u[(i * KP + j) * KP + k];
36+
float err = fabsf(out[(a * KQ + b) * KQ + c] - ref);
37+
if (err > maxErr) maxErr = err;
38+
}
39+
printf("cutensornet tensor product max_err=%g\n", maxErr);
40+
return maxErr <= 2.0e-5f ? 0 : 1;
41+
}

lib/polygeist/Passes/LowerKernelLaunchToCuBLAS.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ static StringRef shimSymbolFor(StringRef libSym) {
124124
return "polygeist_cufft_z2z_1d";
125125
if (libSym == "cufftC2C_1D_tensor")
126126
return "polygeist_cufft_c2c_1d";
127+
if (libSym == "cutensornetTensorProduct3D_f32_tensor")
128+
return "polygeist_cutensornet_tensor_product_3d_f32";
127129
// NOTE: cudnnConvolution2D_9tap_i{8,16} are intentionally absent — those
128130
// launches route to PVA Solutions' libpva_operator and are lowered by
129131
// a separate pass (see LowerKernelLaunchToPVA.cpp). cuDNN itself has
@@ -1007,6 +1009,71 @@ static LogicalResult lowerCufftC2C1DTensor(LaunchOp launch, ModuleOp module,
10071009
return success();
10081010
}
10091011

1012+
// Tensor-product contraction
1013+
// out[a,b,c] = sum(i,j,k) psi[a,i] psi[b,j] psi[c,k] u[i,j,k]
1014+
// reaches the matcher as five rank-6 submap views over three flat buffers.
1015+
// The first three views share the psi base. Unwrap all views and let the
1016+
// cuTensorNet shim express the full Einstein contraction directly.
1017+
static LogicalResult lowerCutensornetTensorProduct3DF32(LaunchOp launch,
1018+
ModuleOp module) {
1019+
if (launch.getNumOperands() != 5 || launch.getNumResults() != 1)
1020+
return launch.emitError(
1021+
"cuTensorNet tensor product: expected 5 operands and 1 result");
1022+
1023+
for (Value operand : launch.getOperands()) {
1024+
auto ty = dyn_cast<RankedTensorType>(operand.getType());
1025+
if (!ty || ty.getRank() != 6 || !ty.getElementType().isF32())
1026+
return launch.emitError(
1027+
"cuTensorNet tensor product: operands must be rank-6 f32 tensors");
1028+
auto submap = operand.getDefiningOp<polygeist::SubmapOp>();
1029+
if (!submap || submap.getSizes().size() != 6)
1030+
return launch.emitError(
1031+
"cuTensorNet tensor product: operands must be rank-6 submaps");
1032+
}
1033+
1034+
Value psi0 = resolveSubmapBase(launch.getOperand(0));
1035+
Value psi1 = resolveSubmapBase(launch.getOperand(1));
1036+
Value psi2 = resolveSubmapBase(launch.getOperand(2));
1037+
Value u = resolveSubmapBase(launch.getOperand(3));
1038+
Value out = resolveSubmapBase(launch.getOperand(4));
1039+
if (psi0 != psi1 || psi0 != psi2)
1040+
return launch.emitError(
1041+
"cuTensorNet tensor product: first three views must share psi base");
1042+
1043+
auto psiTy = dyn_cast<RankedTensorType>(psi0.getType());
1044+
auto uTy = dyn_cast<RankedTensorType>(u.getType());
1045+
auto outTy = dyn_cast<RankedTensorType>(out.getType());
1046+
if (!psiTy || !uTy || !outTy || !psiTy.getElementType().isF32() ||
1047+
!uTy.getElementType().isF32() || !outTy.getElementType().isF32())
1048+
return launch.emitError(
1049+
"cuTensorNet tensor product: submap bases must be f32 tensors");
1050+
1051+
auto firstView = launch.getOperand(0).getDefiningOp<polygeist::SubmapOp>();
1052+
OpBuilder b(launch);
1053+
Location loc = launch.getLoc();
1054+
Value KQ = valueAsI32(b, loc, firstView.getSizes()[0]);
1055+
Value KP = valueAsI32(b, loc, firstView.getSizes()[3]);
1056+
Value psiMr = tensorToMemref(b, loc, psi0);
1057+
Value uMr = tensorToMemref(b, loc, u);
1058+
Value outMr = tensorToMemref(b, loc, out);
1059+
Value psiPtr = memrefBasePtr(b, loc, psiMr);
1060+
Value uPtr = memrefBasePtr(b, loc, uMr);
1061+
Value outPtr = memrefBasePtr(b, loc, outMr);
1062+
1063+
auto ptrTy = LLVM::LLVMPointerType::get(b.getContext());
1064+
SmallVector<Type> argTypes = {b.getI32Type(), b.getI32Type(), ptrTy,
1065+
ptrTy, ptrTy};
1066+
func::FuncOp shim = ensureShimDecl(
1067+
module, "polygeist_cutensornet_tensor_product_3d_f32", argTypes, b);
1068+
b.create<func::CallOp>(loc, shim,
1069+
ValueRange{KQ, KP, psiPtr, uPtr, outPtr});
1070+
1071+
Value updatedOut = memrefToTensor(b, loc, outMr, out.getType());
1072+
rewireLaunchResult(launch, updatedOut);
1073+
launch.erase();
1074+
return success();
1075+
}
1076+
10101077
// Darknet im2col+GEMM reaches the matcher as rank-3 broadcasted submaps:
10111078
// A(m, k, n) -> weights[m, k]
10121079
// B(m, k, n) -> workspace[k, n]
@@ -2974,6 +3041,8 @@ struct LowerKernelLaunchToCuBLASPass
29743041
} else if (libSym == "cufftZ2Z_1D_tensor" ||
29753042
libSym == "cufftC2C_1D_tensor") {
29763043
r = lowerCufftC2C1DTensor(launch, module, shim);
3044+
} else if (libSym == "cutensornetTensorProduct3D_f32_tensor") {
3045+
r = lowerCutensornetTensorProduct3DF32(launch, module);
29773046
} else if (libSym == "cudnnConvolutionFwd_batched") {
29783047
r = lowerCudnnConv2dBatched(launch, module);
29793048
} else if (libSym == "cudnnConvolutionFwd_im2col_gemm") {

0 commit comments

Comments
 (0)