-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wgsl: Add AF Division execution tests #3074
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
export const description = ` | ||
Execution Tests for non-matrix AbstractFloat division expression | ||
`; | ||
|
||
import { makeTestGroup } from '../../../../../common/framework/test_group.js'; | ||
import { GPUTest } from '../../../../gpu_test.js'; | ||
import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js'; | ||
import { FP, FPVector } from '../../../../util/floating_point.js'; | ||
import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js'; | ||
import { makeCaseCache } from '../case_cache.js'; | ||
import { onlyConstInputSource, run } from '../expression.js'; | ||
|
||
import { abstractBinary } from './binary.js'; | ||
|
||
const divisionVectorScalarInterval = (v: number[], s: number): FPVector => { | ||
return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(e, s))); | ||
}; | ||
|
||
const divisionScalarVectorInterval = (s: number, v: number[]): FPVector => { | ||
return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(s, e))); | ||
}; | ||
|
||
export const g = makeTestGroup(GPUTest); | ||
|
||
const scalar_cases = { | ||
['scalar']: () => { | ||
return FP.abstract.generateScalarPairToIntervalCases( | ||
sparseF64Range(), | ||
sparseF64Range(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some data point in this range may be out of |
||
'finite', | ||
FP.abstract.divisionInterval | ||
); | ||
}, | ||
}; | ||
|
||
const vector_scalar_cases = ([2, 3, 4] as const) | ||
.map(dim => ({ | ||
[`vec${dim}_scalar`]: () => { | ||
return FP.abstract.generateVectorScalarToVectorCases( | ||
sparseVectorF64Range(dim), | ||
sparseF64Range(), | ||
'finite', | ||
divisionVectorScalarInterval | ||
); | ||
}, | ||
})) | ||
.reduce((a, b) => ({ ...a, ...b }), {}); | ||
|
||
const scalar_vector_cases = ([2, 3, 4] as const) | ||
.map(dim => ({ | ||
[`scalar_vec${dim}`]: () => { | ||
return FP.abstract.generateScalarVectorToVectorCases( | ||
sparseF64Range(), | ||
sparseVectorF64Range(dim), | ||
'finite', | ||
divisionScalarVectorInterval | ||
); | ||
}, | ||
})) | ||
.reduce((a, b) => ({ ...a, ...b }), {}); | ||
|
||
export const d = makeCaseCache('binary/af_division', { | ||
...scalar_cases, | ||
...vector_scalar_cases, | ||
...scalar_vector_cases, | ||
}); | ||
|
||
g.test('scalar') | ||
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') | ||
.desc( | ||
` | ||
Expression: x / y, where x and y are scalars | ||
Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] | ||
` | ||
) | ||
.params(u => u.combine('inputSource', onlyConstInputSource)) | ||
.fn(async t => { | ||
const cases = await d.get('scalar'); | ||
await run( | ||
t, | ||
abstractBinary('/'), | ||
[TypeAbstractFloat, TypeAbstractFloat], | ||
TypeAbstractFloat, | ||
t.params, | ||
cases | ||
); | ||
}); | ||
|
||
g.test('vector') | ||
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') | ||
.desc( | ||
` | ||
Expression: x / y, where x and y are vectors | ||
Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126] | ||
` | ||
) | ||
.params(u => | ||
u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4] as const) | ||
) | ||
.fn(async t => { | ||
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases | ||
await run( | ||
t, | ||
abstractBinary('/'), | ||
[TypeAbstractFloat, TypeAbstractFloat], | ||
TypeAbstractFloat, | ||
t.params, | ||
cases | ||
); | ||
}); | ||
|
||
g.test('vector_scalar') | ||
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') | ||
.desc( | ||
` | ||
Expression: x / y, where x is a vector and y is a scalar | ||
Accuracy: Correctly rounded | ||
` | ||
) | ||
.params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) | ||
.fn(async t => { | ||
const dim = t.params.dim; | ||
const cases = await d.get(`vec${dim}_scalar`); | ||
await run( | ||
t, | ||
abstractBinary('/'), | ||
[TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat], | ||
TypeVec(dim, TypeAbstractFloat), | ||
t.params, | ||
cases | ||
); | ||
}); | ||
|
||
g.test('scalar_vector') | ||
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') | ||
.desc( | ||
` | ||
Expression: x / y, where x is a scalar and y is a vector | ||
Accuracy: Correctly rounded | ||
` | ||
) | ||
.params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const)) | ||
.fn(async t => { | ||
const dim = t.params.dim; | ||
const cases = await d.get(`scalar_vec${dim}`); | ||
await run( | ||
t, | ||
abstractBinary('/'), | ||
[TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)], | ||
TypeVec(dim, TypeAbstractFloat), | ||
t.params, | ||
cases | ||
); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,10 +40,10 @@ import { | |
map2DArray, | ||
oneULPF16, | ||
oneULPF32, | ||
oneULPF64, | ||
quantizeToF32, | ||
quantizeToF16, | ||
unflatten2DArray, | ||
every2DArray, | ||
} from './math.js'; | ||
|
||
/** Indicate the kind of WGSL floating point numbers being operated on */ | ||
|
@@ -631,12 +631,19 @@ export abstract class FPTraits { | |
public abstract constants(): FPConstants; | ||
|
||
// Utilities - Implemented | ||
|
||
/** @returns an interval containing the point or the original interval */ | ||
public toInterval(n: number | IntervalBounds | FPInterval): FPInterval { | ||
if (n instanceof FPInterval) { | ||
if (n.kind === this.kind) { | ||
return n; | ||
} | ||
|
||
// Preserve if the original interval was unbounded or bounded | ||
if (!n.isFinite()) { | ||
return this.constants().unboundedInterval; | ||
} | ||
|
||
return new FPInterval(this.kind, ...n.bounds()); | ||
} | ||
|
||
|
@@ -700,7 +707,7 @@ export abstract class FPTraits { | |
|
||
/** @returns an FPVector representation of an array of values if possible */ | ||
public toVector(v: (number | IntervalBounds | FPInterval)[]): FPVector { | ||
if (this.isVector(v)) { | ||
if (this.isVector(v) && v.every(e => e.kind === this.kind)) { | ||
return v; | ||
} | ||
|
||
|
@@ -764,7 +771,12 @@ export abstract class FPTraits { | |
|
||
/** @returns an FPMatrix representation of an array of an array of values if possible */ | ||
public toMatrix(m: Array2D<number | IntervalBounds | FPInterval> | FPVector[]): FPMatrix { | ||
if (this.isMatrix(m)) { | ||
if ( | ||
this.isMatrix(m) && | ||
every2DArray(m, (e: FPInterval) => { | ||
return e.kind === this.kind; | ||
}) | ||
) { | ||
return m; | ||
} | ||
|
||
|
@@ -3228,11 +3240,10 @@ export abstract class FPTraits { | |
|
||
// This op is implemented differently for f32 and f16. | ||
private DivisionIntervalOpBuilder(): ScalarPairToIntervalOp { | ||
assert(this.kind === 'f32' || this.kind === 'f16'); | ||
const constants = this.constants(); | ||
const domain_x = [this.toInterval([constants.negative.min, constants.positive.max])]; | ||
const domain_y = | ||
this.kind === 'f32' | ||
this.kind === 'f32' || this.kind === 'abstract' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By using the same But since the spec didn't mention what to expect in that range for af, this would be OK. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Thank you for reading this very closely. The thinking is that we don't want WGSL to specify anything more strict than ECMAScript, and ECMAScript does not specify much in terms of ULP. On the other hand, we also don't want AbstractFloat to be worse than f32. |
||
? [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])] | ||
: [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])]; | ||
return { | ||
|
@@ -3259,7 +3270,6 @@ export abstract class FPTraits { | |
} | ||
|
||
protected divisionIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { | ||
assert(this.kind === 'f32' || this.kind === 'f16'); | ||
return this.runScalarPairToIntervalOp( | ||
this.toInterval(x), | ||
this.toInterval(y), | ||
|
@@ -4727,6 +4737,10 @@ class F32Traits extends FPTraits { | |
public readonly quantizeToF16Interval = this.quantizeToF16IntervalImpl.bind(this); | ||
} | ||
|
||
// Need to separately allocate f32 traits, so they can be referenced by | ||
// FPAbstractTraits for forwarding. | ||
const kF32Traits = new F32Traits(); | ||
|
||
// Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this | ||
// executes before they are defined. | ||
const kAbstractUnboundedInterval = new FPInterval( | ||
|
@@ -4930,14 +4944,18 @@ class FPAbstractTraits extends FPTraits { | |
public readonly isFinite = Number.isFinite; | ||
public readonly isSubnormal = isSubnormalNumberF64; | ||
public readonly flushSubnormal = flushSubnormalNumberF64; | ||
public readonly oneULP = oneULPF64; | ||
public readonly oneULP = (_target: number, _mode: FlushMode = 'flush'): number => { | ||
unreachable(`'FPAbstractTraits.oneULP should never be called`); | ||
}; | ||
public readonly scalarBuilder = abstractFloat; | ||
|
||
// Framework - Fundamental Error Intervals - Overrides | ||
public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this); | ||
public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this); | ||
public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this); | ||
public readonly ulpInterval = this.unboundedUlpInterval.bind(this); | ||
public readonly ulpInterval = (n: number, numULP: number): FPInterval => { | ||
return this.toInterval(kF32Traits.ulpInterval(n, numULP)); | ||
}; | ||
|
||
// Framework - API - Overrides | ||
public readonly absInterval = this.absIntervalImpl.bind(this); | ||
|
@@ -4974,10 +4992,13 @@ class FPAbstractTraits extends FPTraits { | |
'determinantInterval' | ||
); | ||
public readonly distanceInterval = this.unimplementedDistance.bind(this); | ||
public readonly divisionInterval = this.unimplementedScalarPairToInterval.bind( | ||
this, | ||
'divisionInterval' | ||
); | ||
public readonly divisionInterval = ( | ||
x: number | FPInterval, | ||
y: number | FPInterval | ||
): FPInterval => { | ||
return this.toInterval(kF32Traits.divisionInterval(x, y)); | ||
}; | ||
|
||
public readonly dotInterval = this.unimplementedVectorPairToInterval.bind(this, 'dotInterval'); | ||
public readonly expInterval = this.unimplementedScalarToInterval.bind(this, 'expInterval'); | ||
public readonly exp2Interval = this.unimplementedScalarToInterval.bind(this, 'exp2Interval'); | ||
|
@@ -5364,7 +5385,7 @@ class F16Traits extends FPTraits { | |
} | ||
|
||
export const FP = { | ||
f32: new F32Traits(), | ||
f32: kF32Traits, | ||
f16: new F16Traits(), | ||
abstract: new FPAbstractTraits(), | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about introducing some kind of FP mapping dictionary for ULP propose, instead of using
?:
in multiple places?Something in the file scope like
and then use e.g.
or
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done