Skip to content

Commit bd8c0c8

Browse files
committed
fixes to gelu and swiglu approx
1 parent e99c8c4 commit bd8c0c8

4 files changed

Lines changed: 223 additions & 90 deletions

File tree

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.github.gbenroscience.parser.ng.bench;
22

3-
/**
4-
*
5-
* @author GBEMIRO
6-
*/
73
import com.github.gbenroscience.parser.MathExpression;
84
import com.github.gbenroscience.simd.turbo.tools.SIMDVectorTurboEvaluator;
95
import org.openjdk.jmh.annotations.*;
@@ -20,45 +16,41 @@
2016
import org.openjdk.jmh.runner.options.TimeValue;
2117

2218
/**
23-
* Use this to build in the parser-ng-pro directory mvn clean install -Dgpg.skip
24-
* Use this to run the benchmarks
25-
*
26-
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm
19+
* Use this to build in the parser-ng-pro directory: mvn clean install -Dgpg.skip
20+
* Use this to run the benchmarks:
2721
*
28-
* java -jar target/benchmarks.jar SIMDTurboBench -prof perfasm >
29-
* perf_output.txt Now find your specific method's assembly grep -A 50
30-
* "parserNG" perf_output.txt
22+
* java -jar target/benchmarks.jar ActivationBench -prof perfasm
3123
*
3224
* @author GBEMIRO
3325
*/
34-
@BenchmarkMode(Mode.AverageTime) // ns/op
26+
@BenchmarkMode(Mode.AverageTime)
3527
@OutputTimeUnit(TimeUnit.NANOSECONDS)
3628
@State(Scope.Benchmark)
3729
@Fork(value = 3, jvmArgsAppend = {
3830
"-Xms4g", "-Xmx4g",
3931
"-XX:+UnlockDiagnosticVMOptions",
4032
"--add-modules", "jdk.incubator.vector", "-XX:+UnlockDiagnosticVMOptions"
41-
// "-XX:+PrintAssembly", "-XX:CompileCommand=print,*SIMDCompositeExpression.applyMatrixKernel" // uncomment if you have hsdis
4233
})
4334
@Warmup(iterations = 5, time = 2)
4435
@Measurement(iterations = 5, time = 2)
36+
@OperationsPerInvocation(ActivationBench.N) // Tells JMH to divide total time by N to output ns/element
4537
public class ActivationBench {
4638

47-
@Param({"512", "1024", "2048", "4096"})
48-
public int sz;
49-
39+
// Must be a compile-time constant to be used in the annotation above.
40+
// 1048576 elements represents a square matrix of 1024 x 1024.
41+
public static final int N = 2048 * 2048;
42+
43+
private final int sz = (int) Math.sqrt(N);
5044
private SIMDCompositeExpression evaluator;
5145
private FlatMatrixF in1;
5246
private FlatMatrixF in2;
5347
private FlatMatrixF out;
54-
private int N;
5548

5649
@Setup(Level.Trial)
5750
public void setup() throws Throwable {
5851
MathExpression me = new MathExpression("x * 0.5 * (1 + tanh(0.79788456 * (x + 0.044715 * x * x * x)))");
5952
evaluator = (SIMDCompositeExpression) new SIMDVectorTurboEvaluator(me).compile();
6053

61-
N = sz * sz;
6254
in1 = new FlatMatrixF(sz, sz);
6355
in2 = new FlatMatrixF(sz, sz);
6456
out = new FlatMatrixF(sz, sz);
@@ -68,30 +60,23 @@ public void setup() throws Throwable {
6860

6961
@Setup(Level.Iteration) // C2 can't optimize across iterations
7062
public void dirtyInput() {
71-
// JMH will call this before each measurement iteration
72-
// This makes the input unprovable without timing it
63+
// JMH calls this before each measurement iteration to make inputs unpredictable
7364
for (int j = 0; j < N; j++) {
7465
in1.data[j] = ThreadLocalRandom.current().nextFloat();
75-
in2.data[j] = in1.data[j]*2.13f;
66+
in2.data[j] = in1.data[j] * 2.13f;
7667
}
7768
}
7869

7970
@Benchmark
8071
public void gelu(Blackhole bh) {
8172
evaluator.applyMatrixKernel(new FlatMatrixF[]{in1}, out, "gelu");
82-
// Consume the whole output so C2 can't DCE
83-
/*
84-
for (int j = 0; j < N; j++) {
85-
bh.consume(Float.floatToRawIntBits(out.data[j]));
86-
}
87-
*/
88-
bh.consume(out.data[0]);
73+
bh.consume(out); // Completely safe, zero-overhead way to prevent Dead Code Elimination
8974
}
90-
@Benchmark
75+
76+
@Benchmark
9177
public void swiglu(Blackhole bh) {
9278
evaluator.applyMatrixKernel(new FlatMatrixF[]{in1, in2}, out, "swiglu");
93-
// Consume the whole output so C2 can't DCE
94-
bh.consume(out.data[0]);
79+
bh.consume(out);
9580
}
9681

9782
public static void main(String[] args) throws RunnerException {
@@ -112,4 +97,4 @@ public static void main(String[] args) throws RunnerException {
11297

11398
new Runner(configurations).run();
11499
}
115-
}
100+
}

parser-ng-simd/src/main/java/com/github/gbenroscience/simd/turbo/tools/FlatMatrixF.java

Lines changed: 124 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -271,28 +271,84 @@ public void geluInPlace() {
271271
}
272272
}
273273

274-
public void geluInPlaceHighSpeed() {
275-
int len = rows * cols, idx = 0;
276-
if (HAS_VECTOR && isContiguous()) {
274+
/**
275+
* GeGLU in-place: output = inputs[0] ⊙ GELU(inputs[1])
276+
*
277+
* @param gate
278+
*/
279+
public void gegluInPlaceOld(FlatMatrixF gate) { // gate is inputs[1]
280+
if (rows != gate.rows || cols != gate.cols) {
281+
throw new IllegalArgumentException("Shape mismatch for GeGLU");
282+
}
283+
284+
int len = rows * cols;
285+
int idx = 0;
286+
287+
if (HAS_VECTOR && isContiguous() && gate.isContiguous()) {
277288
for (; idx < F_SPECIES.loopBound(len); idx += VF_LEN) {
278-
var v = FloatVector.fromArray(F_SPECIES, data, offset + idx);
289+
var value = FloatVector.fromArray(F_SPECIES, this.data, this.offset + idx);
290+
var g = FloatVector.fromArray(F_SPECIES, gate.data, gate.offset + idx);
279291

280-
// x + 0.044715 * x^3
281-
var x2 = v.mul(v);
282-
var x3 = x2.mul(v);
283-
var inner = v.add(x3.mul(V_GELU_C2)).mul(V_GELU_C1);
292+
// GELU on gate
293+
var g2 = g.mul(g);
294+
var g3 = g2.mul(g);
295+
var inner = g.add(g3.mul(V_GELU_C2)).mul(V_GELU_C1);
296+
var t = inner.lanewise(VectorOperators.TANH);
297+
var geluGate = g.mul(V_HALF).mul(t.add(V_ONE));
284298

285-
// tanh_poly(inner) = inner * (27 + inner^2) / (27 + 9*inner^2)
286-
var t = tanh256(inner); // <- No scalar calls here
299+
// value * GELU(gate)
300+
value.mul(geluGate).intoArray(this.data, this.offset + idx);
301+
}
302+
}
287303

288-
v.mul(V_HALF).mul(t.add(V_ONE)).intoArray(data, offset + idx);
304+
// Scalar fallback
305+
for (; idx < len; idx++) {
306+
float value = this.data[this.offset + idx];
307+
float g = gate.data[gate.offset + idx];
308+
309+
float g3 = g * g * g;
310+
float t = (float) Math.tanh(GELU_C1 * (g + GELU_C2 * g3));
311+
float geluGate = 0.5f * g * (1.0f + t);
312+
313+
this.data[this.offset + idx] = value * geluGate;
314+
}
315+
}
316+
317+
public void gegluInPlace(FlatMatrixF gate) {
318+
if (rows != gate.rows || cols != gate.cols) {
319+
throw new IllegalArgumentException("Shape mismatch for GeGLU");
320+
}
321+
322+
int len = rows * cols;
323+
int idx = 0;
324+
325+
if (HAS_VECTOR && isContiguous() && gate.isContiguous()) {
326+
for (; idx < F_SPECIES.loopBound(len); idx += VF_LEN) {
327+
var value = FloatVector.fromArray(F_SPECIES, this.data, this.offset + idx);
328+
var g = FloatVector.fromArray(F_SPECIES, gate.data, gate.offset + idx);
329+
330+
// GELU(gate)
331+
var g2 = g.mul(g);
332+
var g3 = g2.mul(g);
333+
var inner = g.add(g3.mul(V_GELU_C2)).mul(V_GELU_C1);
334+
var t = inner.lanewise(VectorOperators.TANH);
335+
var geluGate = g.mul(V_HALF).mul(t.add(V_ONE));
336+
337+
// value * GELU(gate)
338+
value.mul(geluGate).intoArray(this.data, this.offset + idx);
289339
}
290340
}
341+
342+
// Scalar fallback
291343
for (; idx < len; idx++) {
292-
float x = data[offset + idx];
293-
float x3 = x * x * x;
294-
float t = (float) Math.tanh(GELU_C1 * (x + GELU_C2 * x3));
295-
data[offset + idx] = 0.5f * x * (1.0f + t);
344+
float value = this.data[this.offset + idx];
345+
float g = gate.data[gate.offset + idx];
346+
347+
float g3 = g * g * g;
348+
float t = (float) Math.tanh(GELU_C1 * (g + GELU_C2 * g3));
349+
float geluGate = 0.5f * g * (1.0f + t);
350+
351+
this.data[this.offset + idx] = value * geluGate;
296352
}
297353
}
298354

@@ -658,9 +714,13 @@ private static void matmulInPlaceScalar(FlatMatrixF A, FlatMatrixF B, FlatMatrix
658714
}
659715

660716
public static void randomFill(FlatMatrixF A) {
717+
randomFill(A, 101);
718+
}//end method randomFill
719+
720+
public static void randomFill(FlatMatrixF A, int max) {
661721
ThreadLocalRandom ran = ThreadLocalRandom.current();
662722
for (int i = 0; i < A.data.length; i++) {
663-
A.data[i] = 1 + ran.nextFloat(101);
723+
A.data[i] = 1 + ran.nextFloat(max);
664724
}
665725
}//end method randomFill
666726

@@ -1009,66 +1069,77 @@ private static void weightedSum(float[] scores, FlatMatrixF V, FlatMatrixF out,
10091069
}
10101070
}
10111071

1012-
// 100% Vector ops. Compiles to vmulpd vaddpd vdivpd only
1013-
private static FloatVector tanh256(FloatVector x) {
1014-
var x2 = x.mul(x);
1015-
var num = x2.add(27.0f); // 27 + x^2
1016-
var den = x2.mul(9.0f).add(27.0f); // 27 + 9*x^2
1017-
return x.mul(num).div(den); // x * num / den
1018-
}
1019-
1020-
/**
1021-
* Fast sigmoid: 1 / (1 + exp(-x)) Max error ~1e-3. ~3x faster than Math.exp
1022-
* scalar. Uses expm1 + rational approx to stay stable.
1023-
*
1024-
* @param x
1025-
* @return
1026-
*/
1027-
public static FloatVector sigmoid(FloatVector x) {
1028-
// 1. Clamp: sig(-inf)=0, sig(+inf)=1
1029-
x = x.max(MIN_X).min(MAX_X);
1030-
1031-
// 2. Use 1 / (1 + exp(-x)) = exp(x) / (1 + exp(x))
1032-
// For x>=0: exp(-x) is small, more stable
1033-
var negX = x.neg();
1034-
var expNegX = fastExp(negX); // exp(-x)
1035-
return ONE.div(ONE.add(expNegX));
1036-
}
1037-
10381072
/**
10391073
* Fast exp approx. Max error ~2e-3 on [-10, 10] Based on Pade approx:
10401074
* exp(x) = 2^(x * log2e)
1075+
*
1076+
* @param x
10411077
*/
10421078
public static FloatVector fastExp(FloatVector x) {
1079+
// Clamp input to avoid float underflow/overflow bounds [-88.0f, 88.0f]
1080+
x = x.lanewise(VectorOperators.MAX, -88.0f)
1081+
.lanewise(VectorOperators.MIN, 88.0f);
1082+
10431083
final FloatVector LOG2E = FloatVector.broadcast(F_SPECIES, 1.44269504f);
10441084
final FloatVector LN2 = FloatVector.broadcast(F_SPECIES, 0.69314718f);
1085+
final FloatVector ONE = FloatVector.broadcast(F_SPECIES, 1.0f);
10451086

1087+
// x * log2(e)
10461088
var fx = x.mul(LOG2E);
10471089

1048-
// float -> int, truncate toward 0
1090+
// STEP 1: Perform manual floor via F2I truncation toward zero
10491091
IntVector ni = (IntVector) fx.convertShape(VectorOperators.F2I, I_SPECIES, 0);
1050-
1051-
// F2I truncates, we need floor. Correct if fx < int(fx)
10521092
FloatVector ni_f = (FloatVector) ni.convertShape(VectorOperators.I2F, F_SPECIES, 0);
1093+
1094+
// For negative values, truncation goes the wrong way.
1095+
// If fx < ni_f, we must subtract 1 from the integer vector.
10531096
var needsDec = fx.compare(VectorOperators.LT, ni_f);
1054-
ni = ni.sub(needsDec.toVector().reinterpretAsInts().lanewise(VectorOperators.ASHR, 31)); // mask->int, subtract 1 where true
1097+
var oneInt = IntVector.broadcast(I_SPECIES, 1);
1098+
1099+
// CORRECT FIX: Cast the Float mask to an Integer mask matching I_SPECIES
1100+
VectorMask<Integer> intMask = needsDec.cast(I_SPECIES);
1101+
ni = ni.sub(oneInt, intMask);
10551102

1056-
var f = fx.sub(ni_f); // fractional part in [0,1)
1103+
// STEP 2: Recompute the corrected float representation of the floor
1104+
ni_f = (FloatVector) ni.convertShape(VectorOperators.I2F, F_SPECIES, 0);
10571105

1058-
// 2^f poly: 1 + y + y^2/2 + y^3/6, y = f*ln2
1106+
// Fractional part is now guaranteed to be in [0, 1)
1107+
var f = fx.sub(ni_f);
1108+
1109+
// STEP 3: 4th-order Polynomial approximation for 2^f
10591110
var y = f.mul(LN2);
10601111
var y2 = y.mul(y);
10611112
var y3 = y2.mul(y);
1062-
var poly = ONE.add(y).add(y2.mul(0.5f)).add(y3.mul(0.16666667f));
1113+
var y4 = y3.mul(y);
1114+
1115+
var poly = ONE.add(y)
1116+
.add(y2.mul(0.5f))
1117+
.add(y3.mul(0.16666667f))
1118+
.add(y4.mul(0.041666668f));
10631119

1064-
// 2^n via bit cast: (n+127)<<23
1065-
IntVector biasedExp = ni.add(127);
1066-
IntVector expBits = biasedExp.lanewise(VectorOperators.LSHL, 23);
1067-
FloatVector pow2n = (FloatVector) expBits.convertShape(VectorOperators.I2F, F_SPECIES, 0);
1120+
// STEP 4: 2^ni via raw bit manipulation: (ni + 127) << 23
1121+
var biased = ni.add(127);
1122+
var bits = biased.lanewise(VectorOperators.LSHL, 23);
1123+
1124+
// Pure bit-cast from integer registers to float registers
1125+
FloatVector pow2n = bits.reinterpretAsFloats();
10681126

10691127
return poly.mul(pow2n);
10701128
}
10711129

1130+
/**
1131+
* Fast sigmoid: 1 / (1 + exp(-x)) Max error ~1e-3. ~3x faster than Math.exp
1132+
* scalar. Uses expm1 + rational approx to stay stable.
1133+
*
1134+
* @param x
1135+
* @return
1136+
*/
1137+
public static FloatVector sigmoid(FloatVector x) {
1138+
final FloatVector ONE = FloatVector.broadcast(F_SPECIES, 1.0f);
1139+
var negX = x.neg();
1140+
return ONE.div(ONE.add(fastExp(negX)));
1141+
}
1142+
10721143

10731144
//////////////////////////////MHA-ATTENTION-DONE/////////////////////////////////////////////
10741145
}

parser-ng-simd/src/main/java/com/github/gbenroscience/simd/turbo/tools/VectorTurboEvaluator.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,7 +1713,7 @@ private void evaluateTile(double[][] _2DVariables, int dataSize, double[] output
17131713
*/
17141714
@Override
17151715
public void applyBulkParallel(double[] flatVariables, double[] output) {
1716-
1716+
17171717
final int length = output.length;
17181718

17191719
if (length < PARALLEL_OPS_THRESHOLD) {
@@ -1785,8 +1785,22 @@ public void applyMatrixKernel(FlatMatrixF[] inputs, FlatMatrixF output, String o
17851785
if (inputs[0] != output) {
17861786
System.arraycopy(inputs[0].data, inputs[0].offset, output.data, output.offset, output.rows * output.cols);
17871787
}
1788-
//output.geluInPlace();
1789-
output.geluInPlaceHighSpeed();
1788+
output.geluInPlace();
1789+
//output.geluInPlaceHighSpeed();
1790+
}
1791+
case "geglu", "geglu_in_place" -> {
1792+
if (inputs.length < 2) {
1793+
throw new IllegalArgumentException("GeGLU requires 2 inputs");
1794+
}
1795+
1796+
// Copy first input to output if they are different
1797+
if (inputs[0] != output) {
1798+
System.arraycopy(inputs[0].data, inputs[0].offset,
1799+
output.data, output.offset,
1800+
output.rows * output.cols);
1801+
}
1802+
1803+
output.gegluInPlace(inputs[1]); // <- gate
17901804
}
17911805
// === New Q8 + Attention kernels ===
17921806
case "q8_quantize" -> {
@@ -2222,7 +2236,7 @@ private boolean isMatrixKernel(String name, int arity) {
22222236
// BLAS
22232237
case "matmul", "gemm", "mm" ->
22242238
arity == 3;
2225-
case "gemv", "matvec" ->
2239+
case "gemv", "matvec","geglu" ->
22262240
arity == 2;
22272241
case "dot", "inner", "axpy" ->
22282242
arity == 3;

0 commit comments

Comments
 (0)