diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/cnn_test.cc | 72 | ||||
| -rw-r--r-- | tools/cnn_v2_test/index.html | 72 |
2 files changed, 128 insertions, 16 deletions
diff --git a/tools/cnn_test.cc b/tools/cnn_test.cc index 3fad2ff..c504c3d 100644 --- a/tools/cnn_test.cc +++ b/tools/cnn_test.cc @@ -46,6 +46,8 @@ struct Args { int num_layers = 3; // Default to 3 layers bool debug_hex = false; // Print first 8 pixels as hex int cnn_version = 1; // 1=CNNEffect, 2=CNNv2Effect + const char* weights_path = nullptr; // Optional .bin weights file + bool cnn_version_explicit = false; // Track if --cnn-version was explicitly set }; // Parse command-line arguments @@ -87,10 +89,13 @@ static bool parse_args(int argc, char** argv, Args* args) { args->debug_hex = true; } else if (strcmp(argv[i], "--cnn-version") == 0 && i + 1 < argc) { args->cnn_version = atoi(argv[++i]); + args->cnn_version_explicit = true; if (args->cnn_version < 1 || args->cnn_version > 2) { fprintf(stderr, "Error: cnn-version must be 1 or 2\n"); return false; } + } else if (strcmp(argv[i], "--weights") == 0 && i + 1 < argc) { + args->weights_path = argv[++i]; } else if (strcmp(argv[i], "--help") == 0) { return false; } else { @@ -99,6 +104,21 @@ static bool parse_args(int argc, char** argv, Args* args) { } } + // Force CNN v2 when --weights is specified + if (args->weights_path) { + if (args->cnn_version_explicit && args->cnn_version != 2) { + fprintf(stderr, "WARNING: --cnn-version %d ignored (--weights forces CNN v2)\n", + args->cnn_version); + } + args->cnn_version = 2; + + // Warn if --layers was specified (binary file config takes precedence) + if (args->num_layers != 3) { // 3 is the default + fprintf(stderr, "WARNING: --layers %d ignored (--weights loads layer config from .bin)\n", + args->num_layers); + } + } + return true; } @@ -108,10 +128,11 @@ static void print_usage(const char* prog) { fprintf(stderr, "\nOPTIONS:\n"); fprintf(stderr, " --blend F Final blend amount (0.0-1.0, default: 1.0)\n"); fprintf(stderr, " --format ppm|png Output format (default: png)\n"); - fprintf(stderr, " --layers N Number of CNN layers (1-10, default: 3)\n"); + fprintf(stderr, " --layers N Number of CNN layers (1-10, default: 3, ignored with --weights)\n"); fprintf(stderr, " --save-intermediates DIR Save intermediate layers to directory\n"); fprintf(stderr, " --debug-hex Print first 8 pixels as hex (debug)\n"); - fprintf(stderr, " --cnn-version N CNN version: 1 (default) or 2\n"); + fprintf(stderr, " --cnn-version N CNN version: 1 (default) or 2 (ignored with --weights)\n"); + fprintf(stderr, " --weights PATH Load weights from .bin (forces CNN v2, overrides layer config)\n"); fprintf(stderr, " --help Show this help\n"); } @@ -586,10 +607,38 @@ static bool process_cnn_v2(WGPUDevice device, WGPUQueue queue, int width, int height, const Args& args) { printf("Using CNN v2 (storage buffer architecture)\n"); - // Load weights + // Load weights (from file or asset system) size_t weights_size = 0; - const uint8_t* weights_data = - (const uint8_t*)GetAsset(AssetId::ASSET_WEIGHTS_CNN_V2, &weights_size); + const uint8_t* weights_data = nullptr; + std::vector<uint8_t> file_weights; // For file-based loading + + if (args.weights_path) { + // Load from file + printf("Loading weights from '%s'...\n", args.weights_path); + FILE* f = fopen(args.weights_path, "rb"); + if (!f) { + fprintf(stderr, "Error: failed to open weights file '%s'\n", args.weights_path); + return false; + } + + fseek(f, 0, SEEK_END); + weights_size = ftell(f); + fseek(f, 0, SEEK_SET); + + file_weights.resize(weights_size); + size_t read = fread(file_weights.data(), 1, weights_size, f); + fclose(f); + + if (read != weights_size) { + fprintf(stderr, "Error: failed to read weights file\n"); + return false; + } + + weights_data = file_weights.data(); + } else { + // Load from asset system + weights_data = (const uint8_t*)GetAsset(AssetId::ASSET_WEIGHTS_CNN_V2, &weights_size); + } if (!weights_data || weights_size < 20) { fprintf(stderr, "Error: CNN v2 weights not available\n"); @@ -635,15 +684,20 @@ static bool process_cnn_v2(WGPUDevice device, WGPUQueue queue, info.out_channels, info.weight_count); } - // Create weights storage buffer + // Create weights storage buffer (skip header + layer info, upload only weights) + size_t header_size = 20; // 5 u32 + size_t layer_info_size = 20 * layer_info.size(); // 5 u32 per layer + size_t weights_offset = header_size + layer_info_size; + size_t weights_only_size = weights_size - weights_offset; + WGPUBufferDescriptor weights_buffer_desc = {}; - weights_buffer_desc.size = weights_size; + weights_buffer_desc.size = weights_only_size; weights_buffer_desc.usage = WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst; weights_buffer_desc.mappedAtCreation = false; WGPUBuffer weights_buffer = wgpuDeviceCreateBuffer(device, &weights_buffer_desc); - wgpuQueueWriteBuffer(queue, weights_buffer, 0, weights_data, weights_size); + wgpuQueueWriteBuffer(queue, weights_buffer, 0, weights_data + weights_offset, weights_only_size); // Create input view const WGPUTextureViewDescriptor view_desc = { @@ -1002,7 +1056,7 @@ static bool process_cnn_v2(WGPUDevice device, WGPUQueue queue, layer_bg_entries[3].binding = 3; layer_bg_entries[3].buffer = weights_buffer; - layer_bg_entries[3].size = weights_size; + layer_bg_entries[3].size = weights_only_size; layer_bg_entries[4].binding = 4; layer_bg_entries[4].buffer = layer_params_buffers[i]; diff --git a/tools/cnn_v2_test/index.html b/tools/cnn_v2_test/index.html index ca89fb4..2ec934d 100644 --- a/tools/cnn_v2_test/index.html +++ b/tools/cnn_v2_test/index.html @@ -543,12 +543,10 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) { } } - if (is_output) { - output[c] = clamp(sum, 0.0, 1.0); - } else if (params.is_layer_0 != 0u) { - output[c] = clamp(sum, 0.0, 1.0); // Layer 0: clamp [0,1] + if (is_output || params.is_layer_0 != 0u) { + output[c] = 1.0 / (1.0 + exp(-sum)); // Sigmoid [0,1] } else { - output[c] = max(0.0, sum); // Middle layers: ReLU + output[c] = max(0.0, sum); // ReLU } } @@ -1395,6 +1393,7 @@ class CNNTester { const label = `Layer ${i - 1}`; html += `<button onclick="tester.visualizeLayer(${i})" id="layerBtn${i}">${label}</button>`; } + html += `<button onclick="tester.saveCompositedLayer()" style="margin-left: 20px; background: #28a745;">Save Composited</button>`; html += '</div>'; html += '<div class="layer-grid" id="layerGrid"></div>'; @@ -1526,7 +1525,7 @@ class CNNTester { continue; } - const vizScale = layerIdx === 0 ? 1.0 : 0.5; // Static: 1.0, CNN layers: 0.5 (4 channels [0,1]) + const vizScale = 1.0; // Always 1.0, shader clamps to [0,1] const paramsBuffer = this.device.createBuffer({ size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST @@ -1618,7 +1617,8 @@ class CNNTester { const layerTex = this.layerOutputs[layerIdx]; if (!layerTex) return; - const vizScale = layerIdx === 0 ? 1.0 : 0.5; + // Always 1.0, shader clamps to [0,1] - show exact layer values + const vizScale = 1.0; const actualChannel = channelOffset + this.selectedChannel; const paramsBuffer = this.device.createBuffer({ @@ -1836,6 +1836,64 @@ class CNNTester { this.setStatus(`Save failed: ${err.message}`, true); } } + + async saveCompositedLayer() { + if (!this.currentLayerIdx) { + this.log('No layer selected for compositing', 'error'); + return; + } + + try { + const canvases = []; + for (let i = 0; i < 4; i++) { + const canvas = document.getElementById(`layerCanvas${i}`); + if (!canvas) { + this.log(`Canvas layerCanvas${i} not found`, 'error'); + return; + } + canvases.push(canvas); + } + + const width = canvases[0].width; + const height = canvases[0].height; + const compositedWidth = width * 4; + + // Create composited canvas + const compositedCanvas = document.createElement('canvas'); + compositedCanvas.width = compositedWidth; + compositedCanvas.height = height; + const ctx = compositedCanvas.getContext('2d'); + + // Composite horizontally + for (let i = 0; i < 4; i++) { + ctx.drawImage(canvases[i], i * width, 0); + } + + // Convert to grayscale + const imageData = ctx.getImageData(0, 0, compositedWidth, height); + const pixels = imageData.data; + for (let i = 0; i < pixels.length; i += 4) { + const gray = 0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]; + pixels[i] = pixels[i + 1] = pixels[i + 2] = gray; + } + ctx.putImageData(imageData, 0, 0); + + // Save as PNG + const blob = await new Promise(resolve => compositedCanvas.toBlob(resolve, 'image/png')); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `composited_layer${this.currentLayerIdx - 1}_${compositedWidth}x${height}.png`; + a.click(); + URL.revokeObjectURL(url); + + this.log(`Saved composited layer: ${a.download}`); + this.setStatus(`Saved: ${a.download}`); + } catch (err) { + this.log(`Failed to save composited layer: ${err.message}`, 'error'); + this.setStatus(`Compositing failed: ${err.message}`, true); + } + } } const tester = new CNNTester(); |
