benchmark-toolbox: CUDA95 integration
This commit is contained in:
1 parent
5dacd30dd3
commit
4fb9615653
7 files changed
+1464
-3
No files matched your search
@@ -0,0 +1,57 @@
|
||||
#version 430 core
|
||||
layout(local_size_x = 16, local_size_y = 16) in;
|
||||
|
||||
layout(std430, binding = 0) readonly buffer DataBuffer {
|
||||
double data[];
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) buffer ResultBuffer {
|
||||
uint result;
|
||||
};
|
||||
|
||||
layout(std140, binding = 0) uniform Params {
|
||||
uint n_rows;
|
||||
uint n_repeats;
|
||||
};
|
||||
|
||||
const double THRESHOLD = 1e-3LF;
|
||||
|
||||
shared uint s_partial[256];
|
||||
|
||||
void main() {
|
||||
uint total_cols = gl_NumWorkGroups.x * gl_WorkGroupSize.x
|
||||
* gl_NumWorkGroups.y * gl_WorkGroupSize.y;
|
||||
|
||||
uint col = (gl_GlobalInvocationID.y * (gl_NumWorkGroups.x * gl_WorkGroupSize.x))
|
||||
+ gl_GlobalInvocationID.x;
|
||||
|
||||
uint count = 0u;
|
||||
|
||||
if (n_rows >= 2u) {
|
||||
for (uint k = 0u; k < n_repeats; ++k) {
|
||||
double bias = double(k) * 1e-12LF;
|
||||
double ref = data[col] + bias;
|
||||
|
||||
for (uint row = 1u; row < n_rows; ++row) {
|
||||
double val = data[row * total_cols + col];
|
||||
if (abs(ref - val) > THRESHOLD)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint lid = gl_LocalInvocationIndex;
|
||||
s_partial[lid] = count;
|
||||
memoryBarrierShared();
|
||||
barrier();
|
||||
|
||||
for (uint s = 128u; s > 0u; s >>= 1) {
|
||||
if (lid < s)
|
||||
s_partial[lid] += s_partial[lid + s];
|
||||
memoryBarrierShared();
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (lid == 0u)
|
||||
atomicAdd(result, s_partial[0]);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#version 430 core
|
||||
layout(local_size_x = 16, local_size_y = 16) in;
|
||||
|
||||
layout(std430, binding = 0) readonly buffer DataBuffer {
|
||||
float data[];
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) buffer ResultBuffer {
|
||||
uint result;
|
||||
};
|
||||
|
||||
layout(std140, binding = 0) uniform Params {
|
||||
uint n_rows;
|
||||
uint n_repeats;
|
||||
};
|
||||
|
||||
const float THRESHOLD = 0.001f;
|
||||
|
||||
// One partial sum to force one atomic per WGP instead of 256.
|
||||
shared uint s_partial[256];
|
||||
|
||||
void main() {
|
||||
uint total_cols = gl_NumWorkGroups.x * gl_WorkGroupSize.x
|
||||
* gl_NumWorkGroups.y * gl_WorkGroupSize.y;
|
||||
|
||||
uint col = (gl_GlobalInvocationID.y * (gl_NumWorkGroups.x * gl_WorkGroupSize.x))
|
||||
+ gl_GlobalInvocationID.x;
|
||||
|
||||
uint count = 0u;
|
||||
|
||||
if (n_rows >= 2u) {
|
||||
for (uint k = 0u; k < n_repeats; ++k) {
|
||||
// Fuck compiler optimizations, imma start throwing bullshit
|
||||
float bias = float(k) * 1e-9;
|
||||
float ref = data[col] + bias;
|
||||
|
||||
for (uint row = 1u; row < n_rows; ++row) {
|
||||
float val = data[row * total_cols + col];
|
||||
if (abs(ref - val) > THRESHOLD)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint lid = gl_LocalInvocationIndex;
|
||||
s_partial[lid] = count;
|
||||
memoryBarrierShared();
|
||||
barrier();
|
||||
|
||||
for (uint s = 128u; s > 0u; s >>= 1) {
|
||||
if (lid < s)
|
||||
s_partial[lid] += s_partial[lid + s];
|
||||
memoryBarrierShared();
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (lid == 0u)
|
||||
atomicAdd(result, s_partial[0]);
|
||||
}
|
||||
@@ -0,0 +1,948 @@
|
||||
#include "cuda95.h"
|
||||
|
||||
#include <switch.h>
|
||||
#include <deko3d.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#ifndef DK_UNIFORM_BUF_ALIGNMENT
|
||||
#define DK_UNIFORM_BUF_ALIGNMENT 0x100
|
||||
#endif
|
||||
|
||||
#define ALIGN_UP(x, a) (((x) + ((a) - 1)) & ~((uint32_t)(a) - 1))
|
||||
|
||||
#define WG_SIZE_X 16
|
||||
#define WG_SIZE_Y 16
|
||||
// groups are dispatched in a 4x4 grid, for 2048 threads total
|
||||
// 2048 per SM = 4096 on TX1, so we fully saturate the GPU
|
||||
#define NUM_GROUPS_X 4
|
||||
#define NUM_GROUPS_Y 4
|
||||
|
||||
#define N_THREADS ((NUM_GROUPS_X * WG_SIZE_X) * (NUM_GROUPS_Y * WG_SIZE_Y)) // 4096
|
||||
#define N_ROWS 64
|
||||
|
||||
// Thread buffers = N_ROWS * N_THREADS, must be power of 2
|
||||
#define N_VEC ((uint64_t)N_ROWS * N_THREADS) // uvec4 elements, 2^18
|
||||
#define N_ELEMS (N_VEC * 4) // uints, 2^20
|
||||
|
||||
static_assert((N_VEC & (N_VEC - 1)) == 0,
|
||||
"N_ROWS * N_THREADS must be a power of two for the scatter mask");
|
||||
|
||||
// Gather size, 8192 * 16B = 128KB per SM. Fits into L2 perfectly
|
||||
#define L2_WINDOW_VEC 8192u
|
||||
#define DISPATCHES_PER_LIST 32
|
||||
#define NUM_INFLIGHT 3
|
||||
#define MAX_RECORDS 64 // fault records per batch, per slot
|
||||
|
||||
#define FENCE_TIMEOUT_NS (5ULL * 1000000000ULL)
|
||||
#define DATA_CHECK_INTERVAL 512
|
||||
|
||||
#define CODEMEM_SIZE (128 * 1024)
|
||||
#define CMDMEM_SIZE (512 * 1024)
|
||||
|
||||
#define DKSH_MAGIC UINT32_C(0x48534B44)
|
||||
|
||||
// TOOLBOX: console drawing removed, log kept as a ring buffer.
|
||||
#define SCR_COLS 79
|
||||
#define LOG_LINES 14
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t header_sz;
|
||||
uint32_t control_sz;
|
||||
uint32_t code_sz;
|
||||
uint32_t programs_off;
|
||||
uint32_t num_programs;
|
||||
} DkshHeaderLocal;
|
||||
|
||||
// Shader parameters
|
||||
typedef struct {
|
||||
uint32_t n_rows;
|
||||
uint32_t n_repeats;
|
||||
uint32_t alu_iters;
|
||||
uint32_t gather_iters;
|
||||
uint32_t max_records;
|
||||
uint32_t mode; // 0 = capture golden, 1 = compare, 2 = ignore
|
||||
uint32_t scatter_mask; // Gather window - 1, used for errors.
|
||||
uint32_t chain_bias;
|
||||
} Params;
|
||||
|
||||
// Result buffer
|
||||
typedef struct {
|
||||
uint32_t mismatches;
|
||||
uint32_t digest;
|
||||
uint32_t selfFail;
|
||||
uint32_t errCount;
|
||||
} GpuResult;
|
||||
|
||||
// Errorlog
|
||||
typedef struct {
|
||||
uint32_t id; // thread index
|
||||
uint32_t stage; // 1 = ALU self-check, 2 = per-thread digest
|
||||
uint32_t got;
|
||||
uint32_t want;
|
||||
} GpuRecord;
|
||||
|
||||
|
||||
// Presets for calibration
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint32_t alu;
|
||||
uint32_t gather;
|
||||
uint32_t window; // 0 = whole buffer
|
||||
} Profile;
|
||||
|
||||
// Testing modes
|
||||
// Balanced tried to hit both
|
||||
// Core forces 2x chains and not as much memory access, although in practice it strains it enough.
|
||||
// Dram scatters reads accross allocated size (4MB, lol, lmao even)
|
||||
// L2 changes this to 256KB.
|
||||
static const Profile kProfiles[] = {
|
||||
{ "balanced", 64, 32, 0 },
|
||||
{ "core", 512, 4, 0 },
|
||||
{ "dram", 8, 256, 0 },
|
||||
{ "l2", 8, 256, L2_WINDOW_VEC },
|
||||
};
|
||||
#define NUM_PROFILES ((int)(sizeof(kProfiles) / sizeof(kProfiles[0])))
|
||||
|
||||
static uint32_t scatterMaskFor(int profile)
|
||||
{
|
||||
uint32_t win = kProfiles[profile].window;
|
||||
if (win == 0u) win = (uint32_t)N_VEC;
|
||||
return win - 1u;
|
||||
}
|
||||
|
||||
// Error storage
|
||||
typedef struct {
|
||||
uint64_t batches;
|
||||
uint64_t detected; // batches where digest=/count
|
||||
uint64_t transient; // corrected by the re-run vote
|
||||
uint64_t persistent; // reproduced on re-run
|
||||
uint64_t selfFail; // ALU DUP/COMP Fails
|
||||
uint64_t dataBad; // corrupt inputs (how did this happen)
|
||||
uint64_t warnings;
|
||||
uint64_t timeouts;
|
||||
} Tally;
|
||||
|
||||
static Tally g_tally;
|
||||
static uint64_t g_tStart;
|
||||
|
||||
static char g_log[LOG_LINES][SCR_COLS + 1];
|
||||
static uint32_t g_logCount;
|
||||
|
||||
// TOOLBOX: shared state
|
||||
static std::thread g_thread;
|
||||
static std::mutex g_lock;
|
||||
static std::atomic<bool> g_stop{ false };
|
||||
static std::atomic<bool> g_running{ false };
|
||||
static cuda95_status_t g_pub{};
|
||||
|
||||
static int s_profile;
|
||||
static unsigned s_repeats;
|
||||
static uint32_t s_goldDigest;
|
||||
static uint32_t s_goldMismatch;
|
||||
static double s_dps;
|
||||
static double s_gbps;
|
||||
static int s_cal;
|
||||
static int s_error;
|
||||
static char s_state[96];
|
||||
|
||||
static int s_cfgProfile;
|
||||
static unsigned s_cfgRepeats;
|
||||
static int s_wantProfile;
|
||||
static unsigned s_wantRepeats;
|
||||
static bool s_wantDirty;
|
||||
|
||||
static uint64_t elapsedSec(void)
|
||||
{
|
||||
return armTicksToNs(armGetSystemTick() - g_tStart) / 1000000000ULL;
|
||||
}
|
||||
|
||||
static void note(const char* fmt, ...)
|
||||
{
|
||||
char body[SCR_COLS + 1];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(body, sizeof(body), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// TOOLBOX: console branch removed
|
||||
uint32_t idx = g_logCount % LOG_LINES;
|
||||
int n = snprintf(g_log[idx], sizeof(g_log[idx]), "[%5llus] ",
|
||||
(unsigned long long)elapsedSec());
|
||||
if (n < 0) n = 0;
|
||||
size_t off = (size_t)n;
|
||||
if (off > sizeof(g_log[idx]) - 1) off = sizeof(g_log[idx]) - 1;
|
||||
size_t room = sizeof(g_log[idx]) - 1 - off;
|
||||
size_t len = strlen(body);
|
||||
if (len > room) len = room;
|
||||
memcpy(g_log[idx] + off, body, len);
|
||||
g_log[idx][off + len] = 0;
|
||||
g_logCount++;
|
||||
}
|
||||
|
||||
// Generate seeds
|
||||
static uint32_t hash32(uint32_t x)
|
||||
{
|
||||
x ^= x >> 16;
|
||||
x *= 0x7FEB352Du;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846CA68Bu;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
// RNG, clamped so nothing fucking explodes too hard
|
||||
static uint32_t dataBits(uint64_t index)
|
||||
{
|
||||
uint32_t bits = hash32((uint32_t)index);
|
||||
uint32_t exp = (bits >> 23) & 0xFFu;
|
||||
if (exp < 0x30u) exp = 0x30u + (exp & 0x0Fu);
|
||||
if (exp > 0xC0u) exp = 0xC0u - (exp & 0x0Fu);
|
||||
return (bits & 0x807FFFFFu) | (exp << 23);
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
DkMemBlock block;
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
} BumpAlloc;
|
||||
|
||||
static uint32_t bumpAlloc(BumpAlloc* a, uint32_t size, uint32_t align)
|
||||
{
|
||||
uint32_t off = ALIGN_UP(a->offset, align);
|
||||
if (off + size > a->size) {
|
||||
note("out of memory (need %u, have %u)", off + size, a->size);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
a->offset = off + size;
|
||||
return off;
|
||||
}
|
||||
|
||||
static void* cpuPtr(BumpAlloc* a, uint32_t off)
|
||||
{
|
||||
return (uint8_t*)dkMemBlockGetCpuAddr(a->block) + off;
|
||||
}
|
||||
|
||||
static DkGpuAddr gpuPtr(BumpAlloc* a, uint32_t off)
|
||||
{
|
||||
return dkMemBlockGetGpuAddr(a->block) + off;
|
||||
}
|
||||
|
||||
// TOOLBOX: copy worker state for the UI thread.
|
||||
static void publish(void)
|
||||
{
|
||||
cuda95_status_t s{};
|
||||
s.batches = g_tally.batches;
|
||||
s.detected = g_tally.detected;
|
||||
s.transient = g_tally.transient;
|
||||
s.persistent = g_tally.persistent;
|
||||
s.selfFail = g_tally.selfFail;
|
||||
s.dataBad = g_tally.dataBad;
|
||||
s.warnings = g_tally.warnings;
|
||||
s.timeouts = g_tally.timeouts;
|
||||
s.dispPerSec = s_dps;
|
||||
s.gbps = s_gbps;
|
||||
s.goldDigest = s_goldDigest;
|
||||
s.goldMismatch = s_goldMismatch;
|
||||
s.elapsedSec = elapsedSec();
|
||||
s.profile = s_profile;
|
||||
s.repeats = s_repeats;
|
||||
s.calibrating = s_cal;
|
||||
s.running = g_running.load() ? 1 : 0;
|
||||
s.error = s_error;
|
||||
snprintf(s.status, sizeof(s.status), "%s", s_state);
|
||||
|
||||
uint32_t total = g_logCount < LOG_LINES ? g_logCount : LOG_LINES;
|
||||
uint32_t keep = total < CUDA95_LOG_LINES ? total : CUDA95_LOG_LINES;
|
||||
s.logLines = keep;
|
||||
for (unsigned i = 0; i < keep; i++) {
|
||||
uint32_t idx = (g_logCount - keep + i) % LOG_LINES;
|
||||
snprintf(s.log[i], sizeof(s.log[i]), "%s", g_log[idx]);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
g_pub = s;
|
||||
}
|
||||
|
||||
static void debugCallback(void* userData, const char* context,
|
||||
DkResult result, const char* message)
|
||||
{
|
||||
(void)userData;
|
||||
if (result == DkResult_Success) {
|
||||
g_tally.warnings++;
|
||||
note("deko3d warn [%s]: %s", context, message);
|
||||
publish();
|
||||
return;
|
||||
}
|
||||
// TOOLBOX: flag the error instead.
|
||||
note("deko3d ERROR [%s]: %s", context, message);
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "GPU error");
|
||||
publish();
|
||||
g_stop.store(true);
|
||||
}
|
||||
|
||||
static bool loadShader(DkShader* shader, BumpAlloc* codeMem, const char* path)
|
||||
{
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) { note("cannot open %s", path); return false; }
|
||||
|
||||
DkshHeaderLocal hdr;
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1 || hdr.magic != DKSH_MAGIC) {
|
||||
note("%s is not a DKSH file", path);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* control = malloc(hdr.control_sz);
|
||||
if (!control) { fclose(f); return false; }
|
||||
|
||||
rewind(f);
|
||||
if (fread(control, hdr.control_sz, 1, f) != 1) {
|
||||
free(control); fclose(f); return false;
|
||||
}
|
||||
|
||||
uint32_t codeOff = bumpAlloc(codeMem, hdr.code_sz, DK_SHADER_CODE_ALIGNMENT);
|
||||
if (codeOff == UINT32_MAX) { free(control); fclose(f); return false; }
|
||||
|
||||
fseek(f, hdr.control_sz, SEEK_SET);
|
||||
if (fread(cpuPtr(codeMem, codeOff), hdr.code_sz, 1, f) != 1) {
|
||||
free(control); fclose(f); return false;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
DkShaderMaker maker;
|
||||
dkShaderMakerDefaults(&maker, codeMem->block, codeOff);
|
||||
maker.control = control;
|
||||
dkShaderInitialize(shader, &maker);
|
||||
|
||||
free(control);
|
||||
return dkShaderIsValid(shader);
|
||||
}
|
||||
|
||||
// Usual Deko3D memory allocation
|
||||
typedef struct {
|
||||
DkQueue queue;
|
||||
DkCmdBuf cmdbuf;
|
||||
DkShader shader;
|
||||
|
||||
DkGpuAddr uboAddr;
|
||||
uint32_t uboSize;
|
||||
|
||||
//3x slots, two are constantly worked on while a third is checked
|
||||
DkGpuAddr resultAddr[NUM_INFLIGHT];
|
||||
GpuResult* resultCpu[NUM_INFLIGHT];
|
||||
DkGpuAddr recAddr[NUM_INFLIGHT];
|
||||
GpuRecord* recCpu[NUM_INFLIGHT];
|
||||
DkCmdList batch[NUM_INFLIGHT];
|
||||
|
||||
DkGpuAddr dataAddr;
|
||||
uint32_t dataSize;
|
||||
uint32_t* dataCpu;
|
||||
|
||||
// Golden digests
|
||||
DkGpuAddr expAddr;
|
||||
uint32_t expSize;
|
||||
} Ctx;
|
||||
|
||||
static void recordBatches(Ctx* c, Params params, uint32_t dispatches)
|
||||
{
|
||||
dkCmdBufClear(c->cmdbuf);
|
||||
|
||||
for (uint32_t s = 0; s < NUM_INFLIGHT; s++) {
|
||||
const DkShader* shaders[] = { &c->shader };
|
||||
dkCmdBufBindShaders(c->cmdbuf, DkStageFlag_Compute, shaders, 1);
|
||||
|
||||
DkBufExtents ubo = { c->uboAddr, c->uboSize };
|
||||
dkCmdBufBindUniformBuffers(c->cmdbuf, DkStage_Compute, 0, &ubo, 1);
|
||||
dkCmdBufPushConstants(c->cmdbuf, c->uboAddr, c->uboSize,
|
||||
0, sizeof(params), ¶ms);
|
||||
|
||||
DkBufExtents ssbos[4];
|
||||
ssbos[0].addr = c->dataAddr; ssbos[0].size = c->dataSize;
|
||||
ssbos[1].addr = c->resultAddr[s]; ssbos[1].size = sizeof(GpuResult);
|
||||
ssbos[2].addr = c->recAddr[s]; ssbos[2].size = MAX_RECORDS * sizeof(GpuRecord);
|
||||
ssbos[3].addr = c->expAddr; ssbos[3].size = c->expSize;
|
||||
dkCmdBufBindStorageBuffers(c->cmdbuf, DkStage_Compute, 0, ssbos, 4);
|
||||
|
||||
for (uint32_t i = 0; i < dispatches; i++)
|
||||
dkCmdBufDispatchCompute(c->cmdbuf, NUM_GROUPS_X, NUM_GROUPS_Y, 1);
|
||||
|
||||
c->batch[s] = dkCmdBufFinishList(c->cmdbuf);
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum { RUN_OK = 0, RUN_TIMEOUT, RUN_QUEUE_ERROR } RunStatus;
|
||||
|
||||
static RunStatus runSync(Ctx* c, uint32_t slot, GpuResult* out, uint64_t* outNs)
|
||||
{
|
||||
memset(c->resultCpu[slot], 0, sizeof(GpuResult));
|
||||
|
||||
DkFence fence;
|
||||
memset(&fence, 0, sizeof(fence));
|
||||
|
||||
uint64_t t0 = armGetSystemTick();
|
||||
dkQueueSubmitCommands(c->queue, c->batch[slot]);
|
||||
// CPU politely asks for the data
|
||||
dkQueueSignalFence(c->queue, &fence, true);
|
||||
dkQueueFlush(c->queue);
|
||||
|
||||
DkResult r = dkFenceWait(&fence, (int64_t)FENCE_TIMEOUT_NS);
|
||||
uint64_t t1 = armGetSystemTick();
|
||||
if (outNs) *outNs = armTicksToNs(t1 - t0);
|
||||
|
||||
// FAHHHHHHH
|
||||
if (r != DkResult_Success)
|
||||
return dkQueueIsInErrorState(c->queue) ? RUN_QUEUE_ERROR : RUN_TIMEOUT;
|
||||
if (dkQueueIsInErrorState(c->queue))
|
||||
return RUN_QUEUE_ERROR;
|
||||
|
||||
*out = *c->resultCpu[slot];
|
||||
return RUN_OK;
|
||||
}
|
||||
|
||||
// Gets reports from GPU results, mainly fault records
|
||||
static void reportRecords(Ctx* c, uint32_t slot, uint32_t errCount)
|
||||
{
|
||||
uint32_t shown = errCount < MAX_RECORDS ? errCount : MAX_RECORDS;
|
||||
if (shown > 3) shown = 3;
|
||||
|
||||
for (uint32_t i = 0; i < shown; i++) {
|
||||
GpuRecord r = c->recCpu[slot][i];
|
||||
note(" rec thread %u %s got %08x want %08x", r.id,
|
||||
r.stage == 1u ? "ALU" : "digest", r.got, r.want);
|
||||
}
|
||||
if (errCount > shown)
|
||||
note(" (%u more records)", errCount - shown);
|
||||
}
|
||||
|
||||
|
||||
// Seperated because this way we can distinguish between source corruption and the GPU on a bender.
|
||||
static uint32_t checkDataIntegrity(Ctx* c)
|
||||
{
|
||||
const uint32_t stride = 997;
|
||||
uint32_t bad = 0;
|
||||
for (uint64_t i = 0; i < N_ELEMS; i += stride)
|
||||
if (c->dataCpu[i] != dataBits(i)) bad++;
|
||||
return bad;
|
||||
}
|
||||
|
||||
|
||||
// Capture golden values to confirm them as valid.
|
||||
// Runs which cannot reproduce their own digests as valid 3 times are assumed as always unstable.
|
||||
// Used to refuse baking in bad data and instead re compute.
|
||||
static bool calibrate(Ctx* c, Params base, uint32_t* goldDigest, uint32_t* goldMismatch)
|
||||
{
|
||||
GpuResult r;
|
||||
Params p = base;
|
||||
|
||||
// Capture data
|
||||
p.mode = 0;
|
||||
recordBatches(c, p, 1);
|
||||
if (runSync(c, 0, &r, NULL) != RUN_OK) {
|
||||
note("calibration: capture run failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare against what we have
|
||||
p.mode = 1;
|
||||
recordBatches(c, p, 1);
|
||||
|
||||
uint32_t d[3], m[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// TOOLBOX: abort calibration when stopped.
|
||||
if (g_stop.load()) return false;
|
||||
if (runSync(c, 0, &r, NULL) != RUN_OK) {
|
||||
note("calibration: confirm run %d failed", i + 1);
|
||||
return false;
|
||||
}
|
||||
d[i] = r.digest;
|
||||
m[i] = r.mismatches;
|
||||
if (r.selfFail || r.errCount) {
|
||||
g_tally.selfFail += r.selfFail;
|
||||
note("calibration: run %d reported %u selfFail, %u records",
|
||||
i + 1, r.selfFail, r.errCount);
|
||||
reportRecords(c, 0, r.errCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (d[0] == d[1] && d[1] == d[2]) {
|
||||
*goldDigest = d[0];
|
||||
*goldMismatch = m[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Weigh the results, if nothing appeared twice, then flag these values.
|
||||
g_tally.detected++;
|
||||
if (d[0] == d[1] || d[0] == d[2]) { *goldDigest = d[0]; *goldMismatch = m[0]; }
|
||||
else if (d[1] == d[2]) { *goldDigest = d[1]; *goldMismatch = m[1]; }
|
||||
else { *goldDigest = d[0]; *goldMismatch = m[0]; }
|
||||
note("CALIBRATION UNSTABLE: %08x %08x %08x -- reference may be wrong",
|
||||
d[0], d[1], d[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TOOLBOX: worker thread, replace runDemo() loop.
|
||||
static void workerMain(void)
|
||||
{
|
||||
g_running.store(true);
|
||||
appletSetAutoSleepDisabled(true);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
s_profile = s_cfgProfile;
|
||||
s_repeats = s_cfgRepeats;
|
||||
}
|
||||
int profile = s_profile;
|
||||
uint32_t repeats = s_repeats;
|
||||
|
||||
memset(&g_tally, 0, sizeof(g_tally));
|
||||
g_logCount = 0;
|
||||
g_tStart = armGetSystemTick();
|
||||
s_goldDigest = 0;
|
||||
s_goldMismatch = 0;
|
||||
s_dps = 0.0;
|
||||
s_gbps = 0.0;
|
||||
s_cal = 0;
|
||||
s_error = 0;
|
||||
snprintf(s_state, sizeof(s_state), "starting");
|
||||
|
||||
Ctx c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
|
||||
note("CUDA95 %d rows x %d threads", N_ROWS, N_THREADS);
|
||||
|
||||
DkDeviceMaker devMaker;
|
||||
dkDeviceMakerDefaults(&devMaker);
|
||||
devMaker.cbDebug = debugCallback;
|
||||
DkDevice device = dkDeviceCreate(&devMaker);
|
||||
|
||||
// Setup queues
|
||||
DkQueueMaker queueMaker;
|
||||
dkQueueMakerDefaults(&queueMaker, device);
|
||||
queueMaker.flags = DkQueueFlags_Compute
|
||||
| DkQueueFlags_MediumPrio
|
||||
| DkQueueFlags_DisableZcull;
|
||||
queueMaker.commandMemorySize = 512 * 1024;
|
||||
queueMaker.flushThreshold = 128 * 1024;
|
||||
DkQueue queue = dkQueueCreate(&queueMaker);
|
||||
c.queue = queue;
|
||||
|
||||
// Allocate and create memory regions
|
||||
DkMemBlockMaker mbMaker;
|
||||
dkMemBlockMakerDefaults(&mbMaker, device, CODEMEM_SIZE);
|
||||
mbMaker.flags = DkMemBlockFlags_CpuUncached
|
||||
| DkMemBlockFlags_GpuCached
|
||||
| DkMemBlockFlags_Code;
|
||||
BumpAlloc codeMem = { dkMemBlockCreate(&mbMaker), 0, CODEMEM_SIZE };
|
||||
|
||||
uint32_t dataSize = (uint32_t)(N_ELEMS * sizeof(uint32_t)); // 4 MiB
|
||||
uint32_t expSize = N_THREADS * sizeof(uint32_t); // 16 KiB
|
||||
uint32_t recSize = MAX_RECORDS * sizeof(GpuRecord); // 1 KiB
|
||||
uint32_t dataMemSize = ALIGN_UP(CMDMEM_SIZE + dataSize + expSize
|
||||
+ NUM_INFLIGHT * (recSize + 0x100) + 0x4000,
|
||||
DK_MEMBLOCK_ALIGNMENT);
|
||||
|
||||
dkMemBlockMakerDefaults(&mbMaker, device, dataMemSize);
|
||||
mbMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
BumpAlloc dataMem = { dkMemBlockCreate(&mbMaker), 0, dataMemSize };
|
||||
|
||||
DkCmdBufMaker cbMaker;
|
||||
dkCmdBufMakerDefaults(&cbMaker, device);
|
||||
c.cmdbuf = dkCmdBufCreate(&cbMaker);
|
||||
|
||||
uint32_t cmdOff = bumpAlloc(&dataMem, CMDMEM_SIZE, DK_CMDMEM_ALIGNMENT);
|
||||
dkCmdBufAddMemory(c.cmdbuf, dataMem.block, cmdOff, CMDMEM_SIZE);
|
||||
|
||||
c.uboSize = ALIGN_UP(sizeof(Params), DK_UNIFORM_BUF_ALIGNMENT);
|
||||
uint32_t uboOff = bumpAlloc(&dataMem, c.uboSize, DK_UNIFORM_BUF_ALIGNMENT);
|
||||
c.uboAddr = gpuPtr(&dataMem, uboOff);
|
||||
|
||||
// Allocate the result slots so we do not stall
|
||||
for (uint32_t s = 0; s < NUM_INFLIGHT; s++) {
|
||||
uint32_t ro = bumpAlloc(&dataMem, sizeof(GpuResult), 0x100);
|
||||
c.resultAddr[s] = gpuPtr(&dataMem, ro);
|
||||
c.resultCpu[s] = (GpuResult*)cpuPtr(&dataMem, ro);
|
||||
|
||||
uint32_t eo = bumpAlloc(&dataMem, recSize, 0x100);
|
||||
c.recAddr[s] = gpuPtr(&dataMem, eo);
|
||||
c.recCpu[s] = (GpuRecord*)cpuPtr(&dataMem, eo);
|
||||
}
|
||||
|
||||
uint32_t expOff = bumpAlloc(&dataMem, expSize, 0x100);
|
||||
c.expAddr = gpuPtr(&dataMem, expOff);
|
||||
c.expSize = expSize;
|
||||
|
||||
uint32_t dataOff = bumpAlloc(&dataMem, dataSize, 0x100);
|
||||
c.dataAddr = gpuPtr(&dataMem, dataOff);
|
||||
c.dataSize = dataSize;
|
||||
c.dataCpu = (uint32_t*)cpuPtr(&dataMem, dataOff);
|
||||
|
||||
// Fill memory
|
||||
note("filling %u KiB of test data...", dataSize / 1024);
|
||||
for (uint64_t i = 0; i < N_ELEMS; i++) {
|
||||
if ((i & 0xFFFFF) == 0 && g_stop.load()) break;
|
||||
c.dataCpu[i] = dataBits(i);
|
||||
}
|
||||
|
||||
uint32_t goldDigest = 0, goldMismatch = 0;
|
||||
uint32_t expDigest = 0, expMismatch = 0;
|
||||
bool ready = false;
|
||||
|
||||
// TOOLBOX: host app owns romfs
|
||||
if (!g_stop.load() && loadShader(&c.shader, &codeMem, "romfs:/shaders/stress_csh.dksh")) {
|
||||
Params base = { N_ROWS, repeats,
|
||||
kProfiles[profile].alu, kProfiles[profile].gather,
|
||||
MAX_RECORDS, 1, scatterMaskFor(profile), 0u };
|
||||
|
||||
s_cal = 1;
|
||||
snprintf(s_state, sizeof(s_state), "calibrating");
|
||||
note("calibrating (%s, repeats %u)...", kProfiles[profile].name, repeats);
|
||||
publish();
|
||||
if (!g_stop.load() && calibrate(&c, base, &goldDigest, &goldMismatch)) {
|
||||
expDigest = goldDigest * DISPATCHES_PER_LIST;
|
||||
expMismatch = goldMismatch * DISPATCHES_PER_LIST;
|
||||
s_goldDigest = goldDigest;
|
||||
s_goldMismatch = goldMismatch;
|
||||
note("golden digest %08x, mismatches %u", goldDigest, goldMismatch);
|
||||
|
||||
Params runParams = base;
|
||||
runParams.mode = 1;
|
||||
recordBatches(&c, runParams, DISPATCHES_PER_LIST);
|
||||
ready = true;
|
||||
} else if (!g_stop.load()) {
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "calibration failed");
|
||||
note("initial calibration failed, aborting");
|
||||
}
|
||||
s_cal = 0;
|
||||
|
||||
if (ready) {
|
||||
// Dispatches are idential and use atomicAdd, so batches must be DISPATCHES_PER_lIST * 1 dispatch.
|
||||
DkFence fences[NUM_INFLIGHT];
|
||||
memset(fences, 0, sizeof(fences));
|
||||
|
||||
uint64_t submitted = 0;
|
||||
uint64_t windowDisp = 0;
|
||||
uint64_t tWindow = armGetSystemTick();
|
||||
double dps = 0.0;
|
||||
bool dirty = false;
|
||||
bool fatal = false;
|
||||
snprintf(s_state, sizeof(s_state), "running");
|
||||
publish();
|
||||
|
||||
while (!g_stop.load() && !fatal) {
|
||||
// TOOLBOX: settings from the UI
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
if (s_wantDirty) {
|
||||
s_wantDirty = false;
|
||||
profile = s_wantProfile;
|
||||
repeats = s_wantRepeats;
|
||||
s_profile = profile;
|
||||
s_repeats = repeats;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
// If we get an error, recapture golden digest for comparisons to actually work.
|
||||
dkQueueWaitIdle(queue);
|
||||
|
||||
base.n_repeats = repeats;
|
||||
base.alu_iters = kProfiles[profile].alu;
|
||||
base.gather_iters = kProfiles[profile].gather;
|
||||
base.scatter_mask = scatterMaskFor(profile);
|
||||
base.mode = 1;
|
||||
|
||||
s_cal = 1;
|
||||
snprintf(s_state, sizeof(s_state), "calibrating");
|
||||
note("recalibrating (%s, repeats %u)...", kProfiles[profile].name, repeats);
|
||||
publish();
|
||||
|
||||
if (g_stop.load()) break;
|
||||
if (!calibrate(&c, base, &goldDigest, &goldMismatch)) {
|
||||
note("recalibration failed");
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "recalibration failed");
|
||||
fatal = true;
|
||||
s_cal = 0;
|
||||
publish();
|
||||
break;
|
||||
}
|
||||
expDigest = goldDigest * DISPATCHES_PER_LIST;
|
||||
expMismatch = goldMismatch * DISPATCHES_PER_LIST;
|
||||
s_goldDigest = goldDigest;
|
||||
s_goldMismatch = goldMismatch;
|
||||
note("calibrated %s repeats %u -> digest %08x",
|
||||
kProfiles[profile].name, repeats, goldDigest);
|
||||
|
||||
Params runParams = base;
|
||||
runParams.mode = 1;
|
||||
recordBatches(&c, runParams, DISPATCHES_PER_LIST);
|
||||
|
||||
memset(fences, 0, sizeof(fences));
|
||||
submitted = 0;
|
||||
dirty = false;
|
||||
s_cal = 0;
|
||||
snprintf(s_state, sizeof(s_state), "running");
|
||||
publish();
|
||||
}
|
||||
|
||||
uint32_t slot = (uint32_t)(submitted % NUM_INFLIGHT);
|
||||
|
||||
if (submitted >= NUM_INFLIGHT) {
|
||||
DkResult r = dkFenceWait(&fences[slot], (int64_t)FENCE_TIMEOUT_NS);
|
||||
|
||||
if (dkQueueIsInErrorState(queue)) {
|
||||
note("FATAL: queue entered error state");
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "GPU queue error");
|
||||
fatal = true;
|
||||
publish();
|
||||
break;
|
||||
}
|
||||
if (r != DkResult_Success) {
|
||||
g_tally.timeouts++;
|
||||
note("FATAL: batch exceeded %llu s deadline",
|
||||
(unsigned long long)(FENCE_TIMEOUT_NS / 1000000000ULL));
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "batch timeout");
|
||||
fatal = true;
|
||||
publish();
|
||||
break;
|
||||
}
|
||||
|
||||
g_tally.batches++;
|
||||
GpuResult res = *c.resultCpu[slot];
|
||||
|
||||
bool bad = (res.digest != expDigest) || (res.mismatches != expMismatch);
|
||||
|
||||
// Shader COMP/DUP still occurs even if digests are bad.
|
||||
// Allows to distinguish what died.
|
||||
if (res.selfFail) {
|
||||
g_tally.selfFail += res.selfFail;
|
||||
note("batch %llu: %u ALU self-check failures",
|
||||
(unsigned long long)g_tally.batches, res.selfFail);
|
||||
bad = true;
|
||||
}
|
||||
if (res.errCount) {
|
||||
reportRecords(&c, slot, res.errCount);
|
||||
bad = true;
|
||||
}
|
||||
|
||||
// We outa stability
|
||||
if (bad) {
|
||||
if (res.digest != expDigest)
|
||||
note("batch %llu: digest %08x want %08x",
|
||||
(unsigned long long)g_tally.batches, res.digest, expDigest);
|
||||
|
||||
g_tally.detected++;
|
||||
|
||||
// Re run batches twice and average, mainly for transience.
|
||||
dkQueueWaitIdle(queue);
|
||||
GpuResult a, b;
|
||||
RunStatus s1 = runSync(&c, slot, &a, NULL);
|
||||
RunStatus s2 = runSync(&c, slot, &b, NULL);
|
||||
if (s1 != RUN_OK || s2 != RUN_OK) {
|
||||
note("FATAL: re-run failed during vote");
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "re-run failed");
|
||||
fatal = true;
|
||||
publish();
|
||||
break;
|
||||
}
|
||||
|
||||
// If we rerun, replay the same list so batches are compared to what they should result in, not the golden value for single dispatches
|
||||
bool aOk = (a.digest == expDigest) && !a.selfFail && !a.errCount;
|
||||
bool bOk = (b.digest == expDigest) && !b.selfFail && !b.errCount;
|
||||
|
||||
// If we detect transience, then we sorta ball, if its persistent, fucking overvolt.
|
||||
if (aOk && bOk) {
|
||||
g_tally.transient++;
|
||||
note(" -> transient, corrected");
|
||||
} else {
|
||||
g_tally.persistent++;
|
||||
note(" -> PERSISTENT (re-runs %08x, %08x)", a.digest, b.digest);
|
||||
}
|
||||
|
||||
// How did this happen
|
||||
// A long time ago
|
||||
uint32_t badElems = checkDataIntegrity(&c);
|
||||
if (badElems) {
|
||||
g_tally.dataBad += badElems;
|
||||
note(" -> input buffer: %u sampled elements wrong", badElems);
|
||||
}
|
||||
|
||||
publish();
|
||||
memset(fences, 0, sizeof(fences));
|
||||
submitted = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// que
|
||||
if ((g_tally.batches % DATA_CHECK_INTERVAL) == 0) {
|
||||
uint32_t badElems = checkDataIntegrity(&c);
|
||||
if (badElems) {
|
||||
g_tally.dataBad += badElems;
|
||||
note("input buffer: %u sampled elements wrong", badElems);
|
||||
}
|
||||
publish();
|
||||
}
|
||||
}
|
||||
|
||||
memset(c.resultCpu[slot], 0, sizeof(GpuResult));
|
||||
dkQueueSubmitCommands(queue, c.batch[slot]);
|
||||
dkQueueSignalFence(queue, &fences[slot], true);
|
||||
dkQueueFlush(queue);
|
||||
|
||||
submitted++;
|
||||
windowDisp += DISPATCHES_PER_LIST;
|
||||
|
||||
uint64_t now = armGetSystemTick();
|
||||
uint64_t windowNs = armTicksToNs(now - tWindow);
|
||||
if (windowNs >= 500000000ULL) {
|
||||
double secs = windowNs / 1.0e9;
|
||||
dps = windowDisp / secs;
|
||||
|
||||
double bytesPer = (double)N_THREADS * 16.0 * repeats
|
||||
* ((double)N_ROWS + (double)kProfiles[profile].gather);
|
||||
|
||||
s_dps = dps;
|
||||
s_gbps = (dps * bytesPer) / 1.0e9;
|
||||
publish();
|
||||
|
||||
windowDisp = 0;
|
||||
tWindow = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!g_stop.load()) {
|
||||
s_error = 1;
|
||||
snprintf(s_state, sizeof(s_state), "shader load failed");
|
||||
note("could not load stress_csh.dksh, aborting");
|
||||
publish();
|
||||
}
|
||||
|
||||
if (!dkQueueIsInErrorState(queue))
|
||||
dkQueueWaitIdle(queue);
|
||||
dkCmdBufDestroy(c.cmdbuf);
|
||||
dkMemBlockDestroy(dataMem.block);
|
||||
dkMemBlockDestroy(codeMem.block);
|
||||
dkQueueDestroy(queue);
|
||||
dkDeviceDestroy(device);
|
||||
|
||||
if (!s_error && g_stop.load())
|
||||
snprintf(s_state, sizeof(s_state), "stopped");
|
||||
publish();
|
||||
|
||||
appletSetAutoSleepDisabled(false);
|
||||
g_running.store(false);
|
||||
publish();
|
||||
}
|
||||
|
||||
static unsigned normRepeats(unsigned r)
|
||||
{
|
||||
if (r < CUDA95_REPEATS_MIN) r = CUDA95_REPEATS_MIN;
|
||||
if (r > CUDA95_REPEATS_MAX) r = CUDA95_REPEATS_MAX;
|
||||
unsigned p = 1;
|
||||
while (p * 2u <= r) p *= 2u;
|
||||
return p;
|
||||
}
|
||||
|
||||
static int normProfile(int p)
|
||||
{
|
||||
if (p < 0) return 0;
|
||||
if (p >= NUM_PROFILES) return NUM_PROFILES - 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void cuda95_start(int profile, unsigned repeats)
|
||||
{
|
||||
cuda95_stop();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
s_cfgProfile = normProfile(profile);
|
||||
s_cfgRepeats = normRepeats(repeats);
|
||||
s_wantProfile = s_cfgProfile;
|
||||
s_wantRepeats = s_cfgRepeats;
|
||||
s_wantDirty = false;
|
||||
memset(&g_pub, 0, sizeof(g_pub));
|
||||
snprintf(g_pub.status, sizeof(g_pub.status), "starting");
|
||||
}
|
||||
|
||||
g_stop.store(false);
|
||||
g_thread = std::thread(workerMain);
|
||||
}
|
||||
|
||||
void cuda95_stop(void)
|
||||
{
|
||||
g_stop.store(true);
|
||||
if (g_thread.joinable())
|
||||
g_thread.join();
|
||||
}
|
||||
|
||||
int cuda95_running(void)
|
||||
{
|
||||
return g_running.load() ? 1 : 0;
|
||||
}
|
||||
|
||||
void cuda95_get(cuda95_status_t *out)
|
||||
{
|
||||
if (!out) return;
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
*out = g_pub;
|
||||
}
|
||||
|
||||
void cuda95_set_profile(int profile)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
s_cfgProfile = normProfile(profile);
|
||||
if (g_running.load()) {
|
||||
s_wantProfile = s_cfgProfile;
|
||||
s_wantDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void cuda95_set_repeats(unsigned repeats)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_lock);
|
||||
s_cfgRepeats = normRepeats(repeats);
|
||||
if (g_running.load()) {
|
||||
s_wantRepeats = s_cfgRepeats;
|
||||
s_wantDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cuda95_profile_name(int profile)
|
||||
{
|
||||
profile = normProfile(profile);
|
||||
return kProfiles[profile].name;
|
||||
}
|
||||
|
||||
unsigned cuda95_profile_alu(int profile)
|
||||
{
|
||||
profile = normProfile(profile);
|
||||
return kProfiles[profile].alu;
|
||||
}
|
||||
|
||||
unsigned cuda95_profile_gather(int profile)
|
||||
{
|
||||
profile = normProfile(profile);
|
||||
return kProfiles[profile].gather;
|
||||
}
|
||||
|
||||
} // extern C
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
// CUDA-95NX backend.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CUDA95_NUM_PROFILES 4
|
||||
|
||||
#define CUDA95_REPEATS_MIN 1u
|
||||
#define CUDA95_REPEATS_MAX 256u
|
||||
|
||||
#define CUDA95_LOG_LINES 8
|
||||
#define CUDA95_LOG_COLS 96
|
||||
|
||||
// Profiles
|
||||
// 0 = balanced
|
||||
// 1 = core
|
||||
// 2 = dram
|
||||
// 3 = l2
|
||||
|
||||
typedef struct {
|
||||
uint64_t batches;
|
||||
uint64_t detected;
|
||||
uint64_t transient;
|
||||
uint64_t persistent;
|
||||
uint64_t selfFail;
|
||||
uint64_t dataBad;
|
||||
uint64_t warnings;
|
||||
uint64_t timeouts;
|
||||
|
||||
double dispPerSec;
|
||||
double gbps;
|
||||
|
||||
uint32_t goldDigest;
|
||||
uint32_t goldMismatch;
|
||||
|
||||
uint64_t elapsedSec;
|
||||
|
||||
int profile;
|
||||
unsigned repeats;
|
||||
|
||||
int calibrating;
|
||||
int running;
|
||||
int error;
|
||||
char status[96];
|
||||
|
||||
char log[CUDA95_LOG_LINES][CUDA95_LOG_COLS];
|
||||
unsigned logLines;
|
||||
} cuda95_status_t;
|
||||
|
||||
void cuda95_start(int profile, unsigned repeats);
|
||||
void cuda95_stop(void);
|
||||
int cuda95_running(void);
|
||||
void cuda95_get(cuda95_status_t *out);
|
||||
|
||||
void cuda95_set_profile(int profile);
|
||||
void cuda95_set_repeats(unsigned repeats);
|
||||
|
||||
const char *cuda95_profile_name(int profile);
|
||||
unsigned cuda95_profile_alu(int profile);
|
||||
unsigned cuda95_profile_gather(int profile);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#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]);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ extern "C" {
|
||||
|
||||
#include "bench.h"
|
||||
#include "cpu_stress.h"
|
||||
#include "cuda95.h"
|
||||
#include "gpu_bw.h"
|
||||
#include "gpu_stress.h"
|
||||
#include "hoc_clk.h"
|
||||
@@ -436,6 +437,126 @@ class StressTab : public brls::Box {
|
||||
brls::Label *statusL, *gflops, *dispatches, *mismatches;
|
||||
};
|
||||
|
||||
class Cuda95Tab : public brls::Box {
|
||||
public:
|
||||
Cuda95Tab() {
|
||||
this->setAxis(brls::Axis::COLUMN);
|
||||
this->setGrow(1.0f);
|
||||
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
|
||||
|
||||
auto *profRow = new brls::Box(brls::Axis::ROW);
|
||||
profRow->setMarginBottom(14.0f);
|
||||
auto *pl = new brls::Label();
|
||||
pl->setText("Profile");
|
||||
pl->setGrow(1.0f);
|
||||
profRow->addView(pl);
|
||||
profVal = new brls::Label();
|
||||
profRow->addView(profVal);
|
||||
this->addView(profRow);
|
||||
|
||||
refreshOpts();
|
||||
|
||||
auto *hint = new brls::Label();
|
||||
hint->setText("L / R: profile. A: start / stop.");
|
||||
hint->setFontSize(15.0f);
|
||||
hint->setTextColor(nvgRGB(150, 150, 150));
|
||||
hint->setMarginBottom(18.0f);
|
||||
this->addView(hint);
|
||||
|
||||
toggle = new brls::Button();
|
||||
toggle->setText("Start");
|
||||
toggle->registerClickAction([this](brls::View *) {
|
||||
onToggle();
|
||||
return true;
|
||||
});
|
||||
this->addView(toggle);
|
||||
|
||||
this->registerAction("Prev profile", brls::ControllerButton::BUTTON_LB, [this](brls::View *) {
|
||||
cycleProfile(-1);
|
||||
return true;
|
||||
});
|
||||
this->registerAction("Next profile", brls::ControllerButton::BUTTON_RB, [this](brls::View *) {
|
||||
cycleProfile(1);
|
||||
return true;
|
||||
});
|
||||
|
||||
statusL = new brls::Label();
|
||||
statusL->setText("Stopped");
|
||||
statusL->setMarginTop(10.0f);
|
||||
statusL->setMarginBottom(8.0f);
|
||||
this->addView(statusL);
|
||||
|
||||
auto *h = new brls::Header();
|
||||
h->setTitle("Info");
|
||||
this->addView(h);
|
||||
rowRate = makeRow(this, "Read rate");
|
||||
rowBatches = makeRow(this, "Batches");
|
||||
rowMismatch = makeRow(this, "Mismatches");
|
||||
}
|
||||
|
||||
~Cuda95Tab() override {
|
||||
if (cuda95_running())
|
||||
cuda95_stop();
|
||||
}
|
||||
|
||||
void willDisappear(bool resetState = false) override {
|
||||
if (cuda95_running())
|
||||
cuda95_stop();
|
||||
brls::Box::willDisappear(resetState);
|
||||
}
|
||||
|
||||
void frame(brls::FrameContext *ctx) override {
|
||||
cuda95_status_t st;
|
||||
cuda95_get(&st);
|
||||
if (cuda95_running() || lastRunning) {
|
||||
rowRate->setText(fstr("%.2f GB/s", st.gbps));
|
||||
rowBatches->setText(fstru("%llu", (unsigned long long)st.batches));
|
||||
unsigned long long mm = (unsigned long long)st.detected + (unsigned long long)st.selfFail
|
||||
+ (unsigned long long)st.dataBad + (unsigned long long)st.timeouts;
|
||||
rowMismatch->setText(fstru("%llu", mm));
|
||||
if (cuda95_running())
|
||||
statusL->setText(st.error ? std::string("Error: ") + st.status : std::string(st.status));
|
||||
}
|
||||
syncToggle(cuda95_running() != 0);
|
||||
brls::Box::frame(ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
void refreshOpts() {
|
||||
char b[64];
|
||||
std::snprintf(b, sizeof b, "%s (%u/%u)", cuda95_profile_name(profile),
|
||||
cuda95_profile_alu(profile), cuda95_profile_gather(profile));
|
||||
profVal->setText(b);
|
||||
}
|
||||
void cycleProfile(int dir) {
|
||||
profile = (profile + dir + CUDA95_NUM_PROFILES) % CUDA95_NUM_PROFILES;
|
||||
refreshOpts();
|
||||
if (cuda95_running())
|
||||
cuda95_set_profile(profile);
|
||||
}
|
||||
void onToggle() {
|
||||
if (cuda95_running()) {
|
||||
cuda95_stop();
|
||||
} else {
|
||||
rowRate->setText("-");
|
||||
rowBatches->setText("-");
|
||||
rowMismatch->setText("-");
|
||||
cuda95_start(profile, 4);
|
||||
}
|
||||
}
|
||||
void syncToggle(bool running) {
|
||||
if (running == lastRunning)
|
||||
return;
|
||||
lastRunning = running;
|
||||
toggle->setText(running ? "Stop" : "Start");
|
||||
if (!running)
|
||||
statusL->setText("Stopped");
|
||||
}
|
||||
int profile = 0;
|
||||
bool lastRunning = false;
|
||||
brls::Label *profVal, *statusL, *rowRate, *rowBatches, *rowMismatch;
|
||||
brls::Button *toggle;
|
||||
};
|
||||
class FurmarkTab : public brls::Box {
|
||||
public:
|
||||
FurmarkTab(int which, const char *desc) : which(which) {
|
||||
@@ -849,6 +970,7 @@ class CreditsTab : public brls::Box {
|
||||
makeRow(this, "Memtester")->setText("Simon Kirby, Charles Cazabon, KazushiMe & CTCaer");
|
||||
makeRow(this, "FurMark")->setText("StanislavPetrovV & AnxietyTimmy");
|
||||
makeRow(this, "GPU Test")->setText("NaGaa95");
|
||||
makeRow(this, "CUDA95")->setText("AnxietyTimmy");
|
||||
makeRow(this, "Membench")->setText("Siarhei Siamashka, KazushiMe & Lineon");
|
||||
makeRow(this, "Stress-ng")->setText("ColinIanKing & Lineon");
|
||||
|
||||
@@ -944,6 +1066,7 @@ class MainActivity : public brls::Activity {
|
||||
|
||||
tab->addHeader("GPU");
|
||||
tab->addTab("GPU Test", [] { return new StressTab(); });
|
||||
tab->addTab("CUDA95", [] { return new Cuda95Tab(); });
|
||||
tab->addTab("Furmark", [] { return new FurmarkTab(0, "FurMark for Switch (48 step)"); });
|
||||
tab->addTab("GPU Path Trace", [] { return new FurmarkTab(2, "GPU Path Tracer"); });
|
||||
|
||||
@@ -988,6 +1111,8 @@ int main(int argc, char *argv[]) {
|
||||
mt_cpu_stop();
|
||||
if (cpu_stress_running())
|
||||
cpu_stress_stop();
|
||||
if (cuda95_running())
|
||||
cuda95_stop();
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
Reference in new issue
Block a user