From 372bd5ca5d6588a41a6166c76ace959797162dbc Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Thu, 9 Jan 2025 05:55:48 -0800 Subject: [PATCH] Reset busy flag on device loss. Tighten try-catch block when getting results from GPU. Add reset function so it can be called automatically on device loss or manually on other errors. --- src/lib/nano-pow/classes/gpu.ts | 158 ++++++++++++++++---------------- 1 file changed, 77 insertions(+), 81 deletions(-) diff --git a/src/lib/nano-pow/classes/gpu.ts b/src/lib/nano-pow/classes/gpu.ts index b4708fb..c6555ad 100644 --- a/src/lib/nano-pow/classes/gpu.ts +++ b/src/lib/nano-pow/classes/gpu.ts @@ -23,7 +23,7 @@ export class NanoPowGpu { } // Initialize WebGPU - static async init () { + static async init (): Promise { if (this.#busy) return this.#busy = true // Request device and adapter @@ -39,14 +39,7 @@ export class NanoPowGpu { if (!(device instanceof GPUDevice)) { throw new Error('WebGPU device failed to load.') } - device.lost.then(loss => { - console.dir(loss) - console.warn(`Device lost. Reinitializing...`) - this.#cpuBuffer?.destroy() - this.#gpuBuffer?.destroy() - this.#uboBuffer?.destroy() - this.init() - }) + device.lost.then(this.reset) this.#device = device this.setup() } catch (err) { @@ -56,9 +49,9 @@ export class NanoPowGpu { } } - static setup () { + static setup (): void { if (this.#device == null) throw new Error(`WebGPU device failed to load.`) - // Create buffers for writing GPU calculations and reading from Javascript + // Create buffers for writing GPU calculations and reading from Javascript this.#uboBuffer = this.#device.createBuffer({ size: 48, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST @@ -104,6 +97,16 @@ export class NanoPowGpu { }) } + static reset (loss?: GPUDeviceLostInfo): void { + console.dir(loss) + console.warn(`Device lost. Reinitializing...`) + this.#cpuBuffer?.destroy() + this.#gpuBuffer?.destroy() + this.#uboBuffer?.destroy() + this.#busy = false + this.init() + } + /** * Finds a nonce that satisfies the Nano proof-of-work requirements. * @@ -121,7 +124,7 @@ export class NanoPowGpu { } this.#busy = true if (!/^[A-Fa-f0-9]{64}$/.test(hash)) throw new TypeError(`Invalid hash ${hash}`) - if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`) + if (typeof threshold !== 'number') throw new TypeError(`Invalid threshold ${threshold}`) // Ensure WebGPU is initialized before calculating let loads = 0 @@ -133,81 +136,74 @@ export class NanoPowGpu { if (this.#device == null) throw new Error(`WebGPU device failed to load.`) let nonce = 0n - let found = false - try { - do { - // Set up uniform buffer object - // Note: u32 size is 4, but total alignment must be multiple of 16 - const uboView = new DataView(new ArrayBuffer(48)) - for (let i = 0; i < 64; i += 8) { - const uint32 = hash.slice(i, i + 8) - uboView.setUint32(i / 2, parseInt(uint32, 16)) - } - const random = Math.floor((Math.random() * 0xffffffff)) - uboView.setUint32(32, random, true) - uboView.setUint32(36, threshold, true) - this.#device.queue.writeBuffer(this.#uboBuffer, 0, uboView) - - // Reset `found` flag to 0u in WORK before each calculation - this.#device.queue.writeBuffer(this.#gpuBuffer, 8, new Uint32Array([0])) - - // Bind UBO read and GPU write buffers - const bindGroup = this.#device.createBindGroup({ - layout: this.#bindGroupLayout, - entries: [ - { - binding: 0, - resource: { - buffer: this.#uboBuffer - }, + do { + // Set up uniform buffer object + // Note: u32 size is 4, but total alignment must be multiple of 16 + const uboView = new DataView(new ArrayBuffer(48)) + for (let i = 0; i < 64; i += 8) { + const uint32 = hash.slice(i, i + 8) + uboView.setUint32(i / 2, parseInt(uint32, 16)) + } + const random = Math.floor((Math.random() * 0xffffffff)) + uboView.setUint32(32, random, true) + uboView.setUint32(36, threshold, true) + this.#device.queue.writeBuffer(this.#uboBuffer, 0, uboView) + + // Reset `found` flag to 0u in WORK before each calculation + this.#device.queue.writeBuffer(this.#gpuBuffer, 8, new Uint32Array([0])) + + // Bind UBO read and GPU write buffers + const bindGroup = this.#device.createBindGroup({ + layout: this.#bindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: this.#uboBuffer }, - { - binding: 1, - resource: { - buffer: this.#gpuBuffer - }, + }, + { + binding: 1, + resource: { + buffer: this.#gpuBuffer }, - ], - }) + }, + ], + }) + + // Create command encoder to issue commands to GPU and initiate computation + const commandEncoder = this.#device.createCommandEncoder() + const passEncoder = commandEncoder.beginComputePass() + + // Issue commands and end compute pass structure + passEncoder.setPipeline(this.#pipeline) + passEncoder.setBindGroup(0, bindGroup) + passEncoder.dispatchWorkgroups(256, 256, 256) + passEncoder.end() + + // Copy 8-byte nonce and 4-byte found flag from GPU to CPU for reading + commandEncoder.copyBufferToBuffer(this.#gpuBuffer, 0, this.#cpuBuffer, 0, 12) + + // End computation by passing array of command buffers to command queue for execution + this.#device.queue.submit([commandEncoder.finish()]) - // Create command encoder to issue commands to GPU and initiate computation - const commandEncoder = this.#device.createCommandEncoder() - const passEncoder = commandEncoder.beginComputePass() - - // Issue commands and end compute pass structure - passEncoder.setPipeline(this.#pipeline) - passEncoder.setBindGroup(0, bindGroup) - passEncoder.dispatchWorkgroups(256, 256, 256) - passEncoder.end() - - // Copy 8-byte nonce and 4-byte found flag from GPU to CPU for reading - commandEncoder.copyBufferToBuffer( - this.#gpuBuffer, - 0, - this.#cpuBuffer, - 0, - 12 - ) - - // End computation by passing array of command buffers to command queue for execution - this.#device.queue.submit([commandEncoder.finish()]) - - // Read results back to Javascript and then unmap buffer after reading + // Read results back to Javascript and then unmap buffer after reading + let data = null + try { await this.#cpuBuffer.mapAsync(GPUMapMode.READ) await this.#device.queue.onSubmittedWorkDone() - const dataBuffer = this.#cpuBuffer.getMappedRange().slice(0) + data = new DataView(this.#cpuBuffer.getMappedRange().slice(0)) + } catch (err) { + console.warn(`Error getting data from GPU. ${err}`) + this.reset() + } finally { this.#cpuBuffer.unmap() + } - if (dataBuffer == null) throw new Error(`Failed to get data from buffer.`) - const dataView = new DataView(dataBuffer) - nonce = dataView.getBigUint64(0, true) - found = !!dataView.getUint32(8) - } while (!found) - } catch (err) { - console.warn(`Error getting data from GPU. ${err}`) - } finally { - this.#busy = false - return nonce.toString(16).padStart(16, '0') - } + if (data == null) throw new Error(`Failed to get data from buffer.`) + nonce = data.getBigUint64(0, true) + this.#busy = !data.getUint32(8) + } while (this.#busy) + return nonce.toString(16).padStart(16, '0') } } -- 2.34.1