benchmark-toolbox: CUDA95 integration

This commit is contained in:
souldbminersmwc committed 2026-09-12 17:17:21 -04:00
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), &params);
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]);
}
}
+125
View File
@@ -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);
}