Skip to content

GpuShaderTrainingSmoke.kt

src/jvmMain/kotlin/afield/graphics/shader/learning/GpuShaderTrainingSmoke.kt · 102 lines

package afield.graphics.shader.learning

import afield.document.graph.Box
import afield.graphics.vulkan.Renderer
import afield.document.context.*
import afield.graphics.render.*
import afield.graphics.scene.*
import afield.graphics.shader.*
import afield.graphics.render.Module
import afield.media.reconstruction.ReconstructionCapture
import afield.graphics.vulkan.targets.RenderToTexture
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import afield.math.linalg.Vector4f
import org.json.JSONObject
import org.lwjgl.vulkan.VK13.*
import java.io.File
import kotlin.system.exitProcess

object GpuShaderTrainingSmoke

@JvmStatic fun main(args: Array<String>)

body — 81 lines, from line 21
    @JvmStatic fun main(args: Array<String>) {
        Options.enableVR=false;Options.enableHDR=false;Options.doVulkanSDK()
        val folder=File("build/neural-tools/gpu-learning").also { it.mkdirs() }
        val window=Window("Field GPU shader learning");val renderer=Renderer(window)
        val box=Box("gpu-learning-check");box.setPropertyRaw("renderer",renderer,doResync=false)
        try {Box.withBoxContextNow(box) {
            fun pump(n: Int=1){val end=renderer.frameCounter+n;while(renderer.frameCounter<end){window.pollEvents();renderer.render()}}
            val source=RenderToTexture(renderer){width=1920;height=1080;useDepth=false;format=VK_FORMAT_R32G32B32A32_SFLOAT}
            val teacher=Module(parameters(renderer){name="editable-teacher";+source;depthTest=false;depthWrite=false;blend=false
                vertex="""#version 460
                    void main(){vec2 p=vec2((gl_VertexIndex<<1)&2,gl_VertexIndex&2);gl_Position=vec4(p*2-1,0,1);}
                """
                fragment=GpuShaderTrainingSmoke::class.java.getResource("/afield/four/neural/teacher.frag")!!.readText()
            })
            check(teacher.valid);val p=teacher.pointer();val mesh=Mesh(renderer,p);mesh.resize(3,0,p)
            mesh.push.setMembers(teacher.fi.pushConstants.single().members);teacher.meshes["quad"]=mesh;source.modules["teacher"]=p
            GpuShaderLearningView(renderer,source){parameter->mesh.push["state"]=Vector4f(source.width.toFloat(),source.height.toFloat(),parameter,0f)}.use { lab ->
                lab.install();lab.learning=false;lab.parameter=.173f;pump(4)
                fun mse(): Double {
                    val a=ReconstructionCapture.read(renderer,source);val b=ReconstructionCapture.read(renderer,lab.model.output)
                    check(a.all { it.isFinite() } && b.all { it.isFinite() })
                    var sum=0.0;var n=0
                    for(i in a.indices)if(i%4!=3){val d=(a[i]-b[i]).toDouble();sum+=d*d;n++};return sum/n
                }
                val initial=mse();println("GPU LEARNING INITIAL $initial")
                // First-step reference catches signed second moments and layout/packing mistakes.
                val old=ClusterGeometrySmoke.readBuffer(renderer,lab.model.weights).asShortBuffer()
                lab.learning=true;lab.iterationsPerFrame=1;lab.parameter=-1f;pump()
                val gradient=ClusterGeometrySmoke.readBuffer(renderer,lab.model.gradients).asShortBuffer()
                val state=ClusterGeometrySmoke.readBuffer(renderer,lab.model.optimizer).asFloatBuffer()
                var nonzero=0
                for(i in 0 until old.remaining()) {
                    val g=(java.lang.Float.float16ToFloat(gradient[i])/128f).coerceIn(-10f,10f)
                    val m=.1f*g;val v=.001f*g*g
                    val w=java.lang.Float.float16ToFloat(old[i])-.001f*g/(kotlin.math.abs(g)+1e-8f)
                    check(state[i*4].isFinite() && kotlin.math.abs(state[i*4]-w)<2e-5f)
                    check(kotlin.math.abs(state[i*4+1]-m)<1e-6f && kotlin.math.abs(state[i*4+2]-v)<1e-6f && state[i*4+2]>=0)
                    if(kotlin.math.abs(g)>.00001f)nonzero++
                }
                check(nonzero>100) { "Training did not produce useful gradients" }
                lab.learning=true;lab.iterationsPerFrame=8
                repeat(500){f->lab.parameter=(f%13)/6f-1;pump()
                    if(f%100==99)println("GPU LEARNING ${lab.model.steps} updates")}
                lab.learning=false;lab.parameter=.173f;pump(8)
                val fitted=mse();println("GPU LEARNING FITTED $fitted")
                check(fitted<.003 && fitted<initial*.05) { "GPU fit did not converge: initial=$initial fitted=$fitted" }
                val stripe=ShaderLearningChecks.stripeMetrics(ReconstructionCapture.read(renderer,source),
                    ReconstructionCapture.read(renderer,lab.model.output),source.width,source.height)
                ReconstructionCapture.save(File(folder,"comparison.png"),ReconstructionCapture.read(renderer,lab.output),source.width,source.height)
                ReconstructionCapture.save(File(folder,"learned.png"),ReconstructionCapture.read(renderer,lab.model.output),source.width,source.height)
                ReconstructionCapture.save(File(folder,"source.png"),ReconstructionCapture.read(renderer,source),source.width,source.height)
                val saved=lab.model.save(File(folder,"models/run-${System.nanoTime()}"),ShaderTraining.sourceDigest(source),
                    "Fixed fullscreen teacher; only normalized time varies")
                val live=ReconstructionCapture.read(renderer,lab.model.output)
                LearnedShader(renderer,source.width,source.height,true,File(saved,"weights.f16"),File(saved,"weights.json"),"checkpoint",false).use { frozen ->
                    frozen.time=lab.parameter;renderer.renderList.preTargets["checkpoint"]=frozen;pump(4)
                    val restored=ReconstructionCapture.read(renderer,frozen.output)
                    check(restored.all { it.isFinite() })
                    val delta=restored.indices.maxOf { kotlin.math.abs(restored[it]-live[it]) }
                    check(delta<.01f) { "Training-optimal to row-major checkpoint mismatch: $delta" }
                    println("GPU LEARNING CHECKPOINT maxAbs=$delta at $saved")
                }
                lab.view=1;val count=lab.sourceRenderCount;pump(16);check(count==lab.sourceRenderCount)
                lab.view=2
                if("--clip" in args)repeat(120){f->lab.parameter=kotlin.math.sin(f*.05236f);pump()
                    ReconstructionCapture.save(File(folder,"clip/%04d.png".format(f)),ReconstructionCapture.read(renderer,lab.output),source.width,source.height)}
                val times=lab.model.gpuMilliseconds.takeLast(100).sorted();check(times.size==100)
                val report=JSONObject().put("initialMse",initial).put("heldOutParameter",.173).put("fittedMse",fitted).put("steps",lab.model.steps)
                    .put("stripe",stripe)
                    .put("batch",lab.model.batchSize).put("iterationsPerFrame",lab.iterationsPerFrame).put("trainingBytes",lab.model.trainingBytes)
                    .put("allocatedBytes",lab.model.allocatedBytes).put("layouts",lab.model.layers.map { it.toString() })
                    .put("trainingMs",JSONObject().put("count",times.size).put("p50",times[50]).put("p95",times[95]))
                File(folder,"measurements.json").writeText(report.toString(2));println(report.toString(2))
                lab.model.reset();lab.view=2;pump(4);check(mse()>fitted*10) { "Reset retained trained model" }
            }
            renderer.cleanup+=Runnable {teacher.cleanup();source.imageViewArray.forEach { it.imageView.cleanup() };source.colorImageRefs.forEach { it?.cleanup() };source.defaultSampler?.cleanup()}
            pump(32)
        }}finally{renderer.cleanup();window.cleanup()}
        println("GPU SHADER TRAINING CHECK DONE");exitProcess(0)
    }
}