203 lines
6.0 KiB
GLSL
203 lines
6.0 KiB
GLSL
#version 430 core
|
|
// CRAWLING IN MY SKINNNNNN
|
|
// THESE WOUNDS THEY WILL NOT HEALLLLLLLL
|
|
// Furmark bored the fuck out of me tbh although the black hole bench I do want to revisit but thats besides the point.
|
|
|
|
// Here we are, this compute shitpost draws more power than furmark.
|
|
|
|
// TX1 has 2SMs with 4 schedulers with 2x issue. So 256 threads per block, 8 blocks per sm, 2048 threads dispatched as a max
|
|
layout(local_size_x = 16, local_size_y = 16) in; // 256 threads = 8 warps
|
|
|
|
|
|
// uvec4 used here to use LDG, essentially forcing data to be read from the unified instruction cache, diferenciating from a global/generic load
|
|
// For us, besdies the speedup this also means that lanes pulls 512 bytes instead of 128, forcing mem/l2 more.
|
|
layout(std430, binding = 0) readonly buffer DataBuffer {
|
|
uvec4 data[];
|
|
};
|
|
|
|
layout(std430, binding = 1) buffer ResultBuffer {
|
|
uint mismatches;
|
|
uint digest;
|
|
uint selfFail;
|
|
uint errCount;
|
|
};
|
|
|
|
// ALU self checks/mismatch storage
|
|
layout(std430, binding = 2) buffer ErrorLog {
|
|
uvec4 record[];
|
|
};
|
|
|
|
layout(std430, binding = 3) buffer ExpectedBuffer {
|
|
uint expected[];
|
|
};
|
|
|
|
layout(std140, binding = 0) uniform Params {
|
|
uint n_rows;
|
|
uint n_repeats;
|
|
uint alu_iters;
|
|
uint gather_iters;
|
|
uint max_records;
|
|
uint mode; // 0 = capture golden, 1 = compare, 2 = ignore
|
|
uint scatter_mask; // limits size to stay in L2 or not
|
|
uint chain_bias; // normally 0; see aluChain2
|
|
};
|
|
|
|
const float THRESHOLD = 0.001f;
|
|
|
|
|
|
// "scratchpad", not really.
|
|
// this is configured for 8kb per block (for 64Kb), any higher drops our block count
|
|
shared uint s_scratch[2048];
|
|
|
|
// FNV-1a. Surpringly heavy because maxwell. (XMAD)
|
|
uint hmix(uint h, uint v)
|
|
{
|
|
h ^= v;
|
|
h *= 16777619u;
|
|
return h;
|
|
}
|
|
|
|
// Constant shifts, so every index below must be a mask/add
|
|
uint xs32(uint x)
|
|
{
|
|
x ^= x << 13;
|
|
x ^= x >> 17;
|
|
x ^= x << 5;
|
|
return x;
|
|
}
|
|
|
|
void pushRecord(uint id, uint stage, uint got, uint want)
|
|
{
|
|
uint idx = atomicAdd(errCount, 1u);
|
|
if (idx < max_records)
|
|
record[idx] = uvec4(id, stage, got, want);
|
|
}
|
|
|
|
|
|
// Man fuck current limits.
|
|
// Here we issue 2 instructions per cycle, intervealing to achive this.
|
|
// chian_bias is only here to stop the compiler from optimizing it away.
|
|
// CPU side passes zero so results must be equal, otherwise we know an error occured.
|
|
// Giving anything else breaks.
|
|
uvec2 aluChain2(uint seed, uint iters)
|
|
{
|
|
uint s2 = seed ^ chain_bias;
|
|
|
|
uint h1 = seed, h2 = s2;
|
|
uint a1 = seed | 1u, a2 = s2 | 1u;
|
|
float f1 = uintBitsToFloat((seed & 0x007FFFFFu) | 0x3F800000u);
|
|
float f2 = uintBitsToFloat((s2 & 0x007FFFFFu) | 0x3F800000u);
|
|
|
|
for (uint i = 0u; i < iters; ++i) {
|
|
// int/FP work
|
|
a1 = xs32(a1) + 0x9E3779B9u;
|
|
a2 = xs32(a2) + 0x9E3779B9u;
|
|
f1 = fma(f1, f1, 0.5);
|
|
f2 = fma(f2, f2, 0.5);
|
|
// Force values back into [1,2].
|
|
f1 = uintBitsToFloat((floatBitsToUint(f1) & 0x007FFFFFu) | 0x3F800000u);
|
|
f2 = uintBitsToFloat((floatBitsToUint(f2) & 0x007FFFFFu) | 0x3F800000u);
|
|
h1 = hmix(h1 ^ a1, floatBitsToUint(f1));
|
|
h2 = hmix(h2 ^ a2, floatBitsToUint(f2));
|
|
}
|
|
return uvec2(h1, h2);
|
|
}
|
|
|
|
// I hate myself
|
|
void main()
|
|
{
|
|
uint gx = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
|
|
uint tid = gl_GlobalInvocationID.y * gx + gl_GlobalInvocationID.x;
|
|
|
|
// Distance in elements between rows. Also serves as the thread count
|
|
uint stride = gx * gl_NumWorkGroups.y * gl_WorkGroupSize.y;
|
|
|
|
uint lid = gl_LocalInvocationIndex;
|
|
|
|
uint count = 0u;
|
|
uint h = 2166136261u ^ tid;
|
|
|
|
for (uint k = 0u; k < n_repeats; ++k) {
|
|
|
|
// read uvec4 data continously
|
|
uvec4 ref = data[tid];
|
|
h = hmix(hmix(hmix(hmix(h, ref.x), ref.y), ref.z), ref.w);
|
|
|
|
vec4 fref = uintBitsToFloat(ref);
|
|
uint idx = tid;
|
|
|
|
for (uint row = 1u; row < n_rows; ++row) {
|
|
|
|
// On reduced load, adss per row instead of IMAD, which because Nvidia would expand into XMAD competing for resorces.
|
|
idx += stride;
|
|
|
|
uvec4 v = data[idx];
|
|
h = hmix(hmix(hmix(hmix(h, v.x), v.y), v.z), v.w);
|
|
|
|
// FSETP + used instead of creating divergence.
|
|
bvec4 hit = greaterThan(abs(fref - uintBitsToFloat(v)), vec4(THRESHOLD));
|
|
count += uint(hit.x) + uint(hit.y) + uint(hit.z) + uint(hit.w);
|
|
}
|
|
|
|
// Gather data in a different line every lane.
|
|
// Sized under L2 max, because fuck cache
|
|
uint st = xs32(h | 1u);
|
|
for (uint g = 0u; g < gather_iters; ++g) {
|
|
st = xs32(st);
|
|
uvec4 v = data[st & scatter_mask];
|
|
h = hmix(hmix(hmix(hmix(h, v.x), v.y), v.z), v.w);
|
|
}
|
|
|
|
// 2x issue redundancy
|
|
uvec2 cc = aluChain2(h, alu_iters);
|
|
if (cc.x != cc.y) {
|
|
atomicAdd(selfFail, 1u);
|
|
pushRecord(tid, 1u, cc.x, cc.y);
|
|
}
|
|
h = cc.x;
|
|
|
|
|
|
// Write back to memory
|
|
for (uint j = 0u; j < 8u; ++j)
|
|
s_scratch[lid + j * 256u] = h + j;
|
|
memoryBarrierShared();
|
|
barrier();
|
|
|
|
uint t = 0u;
|
|
for (uint j = 0u; j < 8u; ++j)
|
|
t = hmix(t, s_scratch[((lid * 37u + 11u) + j * 251u) & 2047u]);
|
|
h = hmix(h, t);
|
|
memoryBarrierShared();
|
|
barrier();
|
|
}
|
|
|
|
// Compare golden samples
|
|
if (mode == 0u) {
|
|
expected[tid] = h;
|
|
} else if (mode == 1u && h != expected[tid]) {
|
|
pushRecord(tid, 2u, h, expected[tid]);
|
|
}
|
|
|
|
|
|
// Fused reduction, digest/count reduced to 8 barriers with one pass instead of 2.
|
|
s_scratch[lid] = h;
|
|
s_scratch[256u + lid] = count;
|
|
memoryBarrierShared();
|
|
barrier();
|
|
|
|
for (uint s = 128u; s > 0u; s >>= 1) {
|
|
if (lid < s) {
|
|
s_scratch[lid] += s_scratch[lid + s];
|
|
s_scratch[256u + lid] += s_scratch[256u + lid + s];
|
|
}
|
|
memoryBarrierShared();
|
|
barrier();
|
|
}
|
|
|
|
// Atomic (16 per WGP)
|
|
if (lid == 0u) {
|
|
atomicAdd(digest, s_scratch[0]);
|
|
atomicAdd(mismatches, s_scratch[256u]);
|
|
}
|
|
}
|