cahlen commited on
Commit
d64c286
Β·
1 Parent(s): 6d132ae

Show WebGL banner with open-in-new-tab link for WebGPU

Browse files
assets/{index-g3y8_Tn1.js β†’ index-BQEvJOi8.js} RENAMED
@@ -31694,4 +31694,4 @@ l0,-`+(t+144)+`c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,
31694
  h &= \\tanh\\bigl(W_1\\, s + b_1\\bigr), & W_1 &\\in \\mathbb{R}^{24 \\times 64},\\; b_1 \\in \\mathbb{R}^{64} \\\\[2pt]
31695
  z &= W_2\\, h + b_2, & W_2 &\\in \\mathbb{R}^{64 \\times 4},\\; b_2 \\in \\mathbb{R}^{4} \\\\[2pt]
31696
  \\pi(a \\mid s) &= \\operatorname{softmax}(z)
31697
- \\end{aligned}`},{kind:"p",parts:["Softmax turns the raw logits into a probability distribution over the 4 actions:"]},{kind:"math",latex:"\\operatorname{softmax}(z)_i \\;=\\; \\frac{\\exp(z_i)}{\\sum_j \\exp(z_j)}"},{kind:"p",parts:["Parameter count: ",_t("24 \\cdot 64 + 64 + 64 \\cdot 4 + 4 = 1\\,860"),". Fast, expressive, standard."]},{kind:"h",text:"Why both?"},{kind:"p",parts:["The CoFrNet is ",Bt("interpretable by construction"),`: because every rung's coefficients are a linear function of the raw input (not of some hidden activation like in an MLP), you can look at the output rung's weights and directly read which input features matter for each action. The feature-attribution panel at the bottom of the CoFrNet viz is exactly that readout β€” live. That's what the authors of the paper mean by "interpretable-by-construction," and it's the whole reason to default to it over the MLP.`]},{kind:"p",parts:["The MLP is included as a comparison baseline β€” a familiar architecture to see the same task solved by a conventional network. Switch to it via the ",ys("brain:")," dropdown."]},{kind:"h",text:"5. Sampling an action"},{kind:"p",parts:["The policy is stochastic. Each tick we sample:"]},{kind:"math",latex:"a_t \\;\\sim\\; \\pi(\\,\\cdot \\mid s_t\\,)"},{kind:"p",parts:["Concretely: draw ",_t("u \\sim \\mathrm{Uniform}(0,1)"),", walk the cumulative sum of ",_t("\\pi"),", pick the first index whose cumsum exceeds ",_t("u"),". This is why the same state can produce different actions β€” the agent is exploring, not deterministic."]},{kind:"h",text:"6. How the network learns β€” REINFORCE"},{kind:"p",parts:["At the end of each episode (goal, death, or timeout), we do one gradient step that nudges the network to make the actions we just took ",Bt("more likely")," when they led to high reward, and ",Bt("less likely")," when they led to low reward."]},{kind:"p",parts:["The per-step signal is the ",Bt("return")," β€” the discounted sum of future rewards from time ",_t("t"),":"]},{kind:"math",latex:"R_t \\;=\\; \\sum_{i=t}^{T} \\gamma^{\\,i-t}\\, r_i, \\qquad \\gamma = 0.99"},{kind:"p",parts:["The objective we maximize is expected return under the policy, ",_t("J(\\theta) = \\mathbb{E}_{\\pi_\\theta}\\!\\left[\\sum_t r_t\\right]"),". The REINFORCE gradient estimator is:"]},{kind:"math",latex:"\\nabla_\\theta J(\\theta) \\;\\approx\\; \\sum_{t=0}^{T} \\nabla_\\theta \\log \\pi_\\theta(a_t \\mid s_t) \\,\\bigl(R_t - b\\bigr)"},{kind:"p",parts:["...where ",_t("b")," is a ",Bt("baseline")," subtracted to reduce variance without biasing the estimate. Classical choices are a running mean of episode returns, or a learned value function ",_t("V_\\phi(s)"),"."]},{kind:"p",parts:["In code we implement this as a loss to ",Bt("minimize"),":"]},{kind:"math",latex:"\\mathcal{L}(\\theta) \\;=\\; -\\sum_{t=0}^{T} \\log \\pi_\\theta(a_t \\mid s_t) \\,\\bigl(R_t - b\\bigr)"},{kind:"p",parts:["One Adam step (learning rate ",_t("3 \\times 10^{-3}"),") per episode."]},{kind:"note",parts:[Bt("Implementation caveat: "),"the current code uses ",_t("b = 0")," (raw returns as advantages) rather than the running-mean baseline the spec prescribed. The baseline was dropped during implementation because, on the deterministic unit test, the mean-of-episode-total baseline pushed action probabilities the wrong way. Real training still works β€” it's just higher variance than it would be with a proper per-step baseline. A value-function baseline (actor-critic) would be the natural upgrade."]},{kind:"h",text:"7. Reading the visualizations"},{kind:"p",parts:["The left panel renders a different view depending on which architecture is active."]},{kind:"h",text:"7a. CoFrNet mode (default)"},{kind:"p",parts:["Four color-coded ladder columns β€” ",ys("noop")," (slate), ",ys("left")," (crimson), ",ys("right")," (green), ",ys("jump")," (amber) β€” each showing the ",_t("d + 1 = 9")," rungs of its continued fraction, with ",_t("k = 0")," at the top (the output logit) and ",_t("k = d = 8")," at the bottom."]},{kind:"ul",items:[[Bt("Rung discs"),": size ∝ ",_t("|a_{\\ell, k}|"),". Disc color is the ladder's color when ",_t("a_{\\ell, k} \\ge 0"),", rose when negative."],[Bt("Probability bars")," at the top of each column show ",_t("\\pi_\\ell")," live, with the percentage below."],[Bt("The hot ladder")," (the argmax action) has a brighter backdrop, a bolded label, haloed rungs, and an ",_t("a_0")," readout next to its top rung."],[Bt("Upward arrows")," between rungs brighten with ",_t("|\\operatorname{saferecip}(V_{\\ell, k+1})|")," β€” the magnitude of the reciprocal term flowing up through the fraction. Bright arrows = the hot path through the CF."],[Bt("The traveling pulse")," on each column is decorative, a reminder that the CF is evaluated bottom-up."]]},{kind:"h",text:"7b. Feature attribution (CoFrNet only)"},{kind:"p",parts:["The footer of the CoFrNet viz answers the question ",Bt("why did the agent pick this action?")," Let ",_t("\\ell^\\ast = \\arg\\max_\\ell \\pi_\\ell")," be the chosen action. Since the output rung of its ladder is the linear term ",_t("a_{\\ell^\\ast, 0} = W_{\\ell^\\ast, 0}^\\top x + b_{\\ell^\\ast, 0}"),", the contribution of input feature ",_t("j")," to the output is directly ",_t("W_{\\ell^\\ast, 0, j} \\cdot x_j"),". The panel ranks features by:"]},{kind:"math",latex:"\\text{contribution}_j \\;=\\; |\\, W_{\\ell^\\ast, 0, j} \\cdot x_j \\,|"},{kind:"p",parts:["and shows the top 5 as horizontal bars with their real names (",ys("plat1.dx"),", ",ys("gap2.w"),", etc.). Bar color is the ladder's color when the signed contribution is positive, rose when negative. This is the interpretability payoff: you see ",Bt("in real time")," which pieces of the world are driving the decision the agent just took."]},{kind:"h",text:"7c. MLP mode"},{kind:"p",parts:["Three vertical columns of circles, ",Bt("left to right:")]},{kind:"ul",items:[[Bt("Input layer")," (24 circles) β€” the current observation ",_t("s"),". Radius ∝ ",_t("|s_i|"),". Green = positive, pink = negative."],[Bt("Hidden layer")," (64 circles, 8Γ—8 grid) β€” the tanh activations ",_t("h"),". Same coloring; brightness ∝ ",_t("|h_i|"),"."],[Bt("Output layer")," (4 circles) β€” the action probabilities ",_t("\\pi"),". The argmax glows brightest; its label appears in the top-right corner of the panel."]]},{kind:"p",parts:[Bt("Edges")," between layers are rendered with opacity proportional to the instantaneous contribution of one neuron to the next:"]},{kind:"math",latex:"\\alpha(i, j) \\;=\\; \\operatorname{clip}\\!\\bigl(\\,|\\,a_i \\cdot w_{ij}\\,|,\\; 0,\\; 1\\bigr)"},{kind:"p",parts:["...so the lines you see lit up are the ",Bt("currently active pathways"),": pairs where the source neuron's activation is large ",Bt("and")," the connecting weight is large. Dim edges mean that connection isn't doing much for this particular input."]},{kind:"h",text:"8. What to expect over time"},{kind:"ul",items:[[Bt("Episodes 0–10.")," The policy is ~uniform random. The agent wanders, falls off a lot. Reward per episode hovers near zero or slightly negative."],[Bt("Episodes 10–50.")," The shaping reward ",_t("+0.01 \\cdot \\Delta x"),` biases the policy toward "move right." You'll see rightward momentum build. The reward sparkline starts trending up.`],[Bt("Episodes 50–200.")," The agent learns ",Bt("when")," to jump by associating gap features (dims 16–23) with the jump action. Occasionally it clears the level."],[Bt("Episodes 200+.")," The policy stabilizes. The agent clears most levels reliably on the fixed seed. Change the seed from the footer and it'll take a while to adapt to the new layout β€” the net generalizes, but only moderately on this small an architecture."]]},{kind:"hr"},{kind:"h",text:"Running notes"},{kind:"p",parts:[Bt("Backend"),": TensorFlow.js 4.x with its WebGPU backend. Every matrix multiply in the forward pass (",_t("24 \\to 64"),", ",_t("64 \\to 4"),") and every gradient in the backward pass runs as a WebGPU compute shader. Change the seed + Reset to spawn a fresh network on a fresh level."]},{kind:"p",parts:["Design specs: ",ys("docs/design.md")," (base project), ",ys("docs/cofrnet.md")," (CoFrNet integration)."]},{kind:"p",parts:["CoFrNet paper: Puri, Dhurandhar, Pedapati, Shanmugam, Wei, Varshney, ",Bt("CoFrNets: Interpretable Neural Architecture Inspired by Continued Fractions"),", NeurIPS 2021 β€” ",ys("arxiv.org/abs/2506.05586"),"."]},{kind:"p",parts:["Press ",ys("Esc")," or click outside to close."]}];async function _1e(){if(typeof navigator<"u"&&navigator.gpu&&await navigator.gpu.requestAdapter().catch(()=>null))try{if(await Ag("webgpu"),await Rg(),rx()==="webgpu")return"webgpu"}catch{}try{if(await Ag("webgl"),await Rg(),rx()==="webgl")return"webgl"}catch{}return await Ag("cpu"),await Rg(),"cpu"}function qS(n){return n==="mlp"?new _me:new Tme}function T1e(){const n=document.createElement("div");Object.assign(n.style,{position:"fixed",top:"0",left:"0",right:"0",zIndex:"200",background:"#2a1a00",color:"#ffcc66",padding:"8px 16px",font:"13px ui-monospace, Menlo, Consolas, monospace",textAlign:"center",borderBottom:"1px solid #664400"});const e=document.createElement("a");e.href=window.location.href,e.target="_blank",e.rel="noopener",e.textContent="Open in a new tab",e.style.color="#66ccff",e.style.textDecoration="underline",n.append("No GPU access in this iframe β€” running on CPU (slow). ",e," for full-speed WebGPU."),document.body.appendChild(n)}async function I1e(){const n=await _1e();console.log(`neuron-runner: using backend=${n} (tf reports: ${rx()})`);const e=document.getElementById("viz-panel"),t=document.getElementById("game-panel"),s=document.getElementById("stats-footer");n==="cpu"&&T1e();let r=1,i=!1,o=0,a="cofrnet",l=0;const c=new aue;c.reset(r);let u=qS(a),h=new hS(u);const d=new Cme(t),p=new Ime(e),f=new Ame(e),x=new Rme(s,{onReset:b=>{r=b|0,c.reset(r),h.startEpisode(),o=0},onTogglePause:()=>(i=!i,i),onArchChange:b=>{b!==a&&(a=b,h.startEpisode(),u.dispose(),u=qS(a),h=new hS(u),c.reset(r),o=0,l=0,h.startEpisode())}});new w1e,h.startEpisode();let m=0;const g=n==="webgpu"?1:4;function v(){m+=1;const b=m%g===0;if(!i){const y=c.obs(),w=Zs(y),C=u.forward(w),_=C.probs.dataSync(),k=Math.random();let R=0,T=0;for(let L=0;L<_.length;L++)if(R+=_[L],k<R){T=L;break}h.record(w,T);const E=c.step(T);if(h.completeReward(E.reward),l+=E.reward,b){if(a==="mlp"&&C.activations.kind==="mlp"){f.setVisible(!1),p.setVisible(!0);const L=C.activations.hidden.dataSync(),B=u,M=B.w1.dataSync(),P=B.w2.dataSync();p.draw({obs:y,hidden:L,probs:_,action:T,w1:M,w2:P})}else if(a==="cofrnet"&&C.activations.kind==="cofrnet"){p.setVisible(!1),f.setVisible(!0);const L=C.activations.A.dataSync(),B=C.activations.V.dataSync(),P=u.W.dataSync();let F=0,q=-1/0;for(let U=0;U<_.length;U++)_[U]>q&&(q=_[U],F=U);f.draw({obs:y,A:L,V:B,W:P,probs:_,argmax:F})}}d.draw(c),E.done&&(h.endEpisode(),x.pushEpisodeReward(l),x.pushLoss(h.lastLoss),o+=1,l=0,c.reset(r+o),h.startEpisode()),Nt([w,C.probs]),C.activations.kind==="mlp"?Nt([C.activations.hidden]):Nt([C.activations.A,C.activations.V])}m%15===0&&(x.setStatus(`backend=${n} | arch=${a} | episode=${o} | tensors=${sx().numTensors}`),x.draw()),requestAnimationFrame(v)}requestAnimationFrame(v)}I1e().catch(n=>{console.error(n);const e=document.getElementById("stats-footer");e&&(e.textContent=`ERROR: ${String(n)}`)});
 
31694
  h &= \\tanh\\bigl(W_1\\, s + b_1\\bigr), & W_1 &\\in \\mathbb{R}^{24 \\times 64},\\; b_1 \\in \\mathbb{R}^{64} \\\\[2pt]
31695
  z &= W_2\\, h + b_2, & W_2 &\\in \\mathbb{R}^{64 \\times 4},\\; b_2 \\in \\mathbb{R}^{4} \\\\[2pt]
31696
  \\pi(a \\mid s) &= \\operatorname{softmax}(z)
31697
+ \\end{aligned}`},{kind:"p",parts:["Softmax turns the raw logits into a probability distribution over the 4 actions:"]},{kind:"math",latex:"\\operatorname{softmax}(z)_i \\;=\\; \\frac{\\exp(z_i)}{\\sum_j \\exp(z_j)}"},{kind:"p",parts:["Parameter count: ",_t("24 \\cdot 64 + 64 + 64 \\cdot 4 + 4 = 1\\,860"),". Fast, expressive, standard."]},{kind:"h",text:"Why both?"},{kind:"p",parts:["The CoFrNet is ",Bt("interpretable by construction"),`: because every rung's coefficients are a linear function of the raw input (not of some hidden activation like in an MLP), you can look at the output rung's weights and directly read which input features matter for each action. The feature-attribution panel at the bottom of the CoFrNet viz is exactly that readout β€” live. That's what the authors of the paper mean by "interpretable-by-construction," and it's the whole reason to default to it over the MLP.`]},{kind:"p",parts:["The MLP is included as a comparison baseline β€” a familiar architecture to see the same task solved by a conventional network. Switch to it via the ",ys("brain:")," dropdown."]},{kind:"h",text:"5. Sampling an action"},{kind:"p",parts:["The policy is stochastic. Each tick we sample:"]},{kind:"math",latex:"a_t \\;\\sim\\; \\pi(\\,\\cdot \\mid s_t\\,)"},{kind:"p",parts:["Concretely: draw ",_t("u \\sim \\mathrm{Uniform}(0,1)"),", walk the cumulative sum of ",_t("\\pi"),", pick the first index whose cumsum exceeds ",_t("u"),". This is why the same state can produce different actions β€” the agent is exploring, not deterministic."]},{kind:"h",text:"6. How the network learns β€” REINFORCE"},{kind:"p",parts:["At the end of each episode (goal, death, or timeout), we do one gradient step that nudges the network to make the actions we just took ",Bt("more likely")," when they led to high reward, and ",Bt("less likely")," when they led to low reward."]},{kind:"p",parts:["The per-step signal is the ",Bt("return")," β€” the discounted sum of future rewards from time ",_t("t"),":"]},{kind:"math",latex:"R_t \\;=\\; \\sum_{i=t}^{T} \\gamma^{\\,i-t}\\, r_i, \\qquad \\gamma = 0.99"},{kind:"p",parts:["The objective we maximize is expected return under the policy, ",_t("J(\\theta) = \\mathbb{E}_{\\pi_\\theta}\\!\\left[\\sum_t r_t\\right]"),". The REINFORCE gradient estimator is:"]},{kind:"math",latex:"\\nabla_\\theta J(\\theta) \\;\\approx\\; \\sum_{t=0}^{T} \\nabla_\\theta \\log \\pi_\\theta(a_t \\mid s_t) \\,\\bigl(R_t - b\\bigr)"},{kind:"p",parts:["...where ",_t("b")," is a ",Bt("baseline")," subtracted to reduce variance without biasing the estimate. Classical choices are a running mean of episode returns, or a learned value function ",_t("V_\\phi(s)"),"."]},{kind:"p",parts:["In code we implement this as a loss to ",Bt("minimize"),":"]},{kind:"math",latex:"\\mathcal{L}(\\theta) \\;=\\; -\\sum_{t=0}^{T} \\log \\pi_\\theta(a_t \\mid s_t) \\,\\bigl(R_t - b\\bigr)"},{kind:"p",parts:["One Adam step (learning rate ",_t("3 \\times 10^{-3}"),") per episode."]},{kind:"note",parts:[Bt("Implementation caveat: "),"the current code uses ",_t("b = 0")," (raw returns as advantages) rather than the running-mean baseline the spec prescribed. The baseline was dropped during implementation because, on the deterministic unit test, the mean-of-episode-total baseline pushed action probabilities the wrong way. Real training still works β€” it's just higher variance than it would be with a proper per-step baseline. A value-function baseline (actor-critic) would be the natural upgrade."]},{kind:"h",text:"7. Reading the visualizations"},{kind:"p",parts:["The left panel renders a different view depending on which architecture is active."]},{kind:"h",text:"7a. CoFrNet mode (default)"},{kind:"p",parts:["Four color-coded ladder columns β€” ",ys("noop")," (slate), ",ys("left")," (crimson), ",ys("right")," (green), ",ys("jump")," (amber) β€” each showing the ",_t("d + 1 = 9")," rungs of its continued fraction, with ",_t("k = 0")," at the top (the output logit) and ",_t("k = d = 8")," at the bottom."]},{kind:"ul",items:[[Bt("Rung discs"),": size ∝ ",_t("|a_{\\ell, k}|"),". Disc color is the ladder's color when ",_t("a_{\\ell, k} \\ge 0"),", rose when negative."],[Bt("Probability bars")," at the top of each column show ",_t("\\pi_\\ell")," live, with the percentage below."],[Bt("The hot ladder")," (the argmax action) has a brighter backdrop, a bolded label, haloed rungs, and an ",_t("a_0")," readout next to its top rung."],[Bt("Upward arrows")," between rungs brighten with ",_t("|\\operatorname{saferecip}(V_{\\ell, k+1})|")," β€” the magnitude of the reciprocal term flowing up through the fraction. Bright arrows = the hot path through the CF."],[Bt("The traveling pulse")," on each column is decorative, a reminder that the CF is evaluated bottom-up."]]},{kind:"h",text:"7b. Feature attribution (CoFrNet only)"},{kind:"p",parts:["The footer of the CoFrNet viz answers the question ",Bt("why did the agent pick this action?")," Let ",_t("\\ell^\\ast = \\arg\\max_\\ell \\pi_\\ell")," be the chosen action. Since the output rung of its ladder is the linear term ",_t("a_{\\ell^\\ast, 0} = W_{\\ell^\\ast, 0}^\\top x + b_{\\ell^\\ast, 0}"),", the contribution of input feature ",_t("j")," to the output is directly ",_t("W_{\\ell^\\ast, 0, j} \\cdot x_j"),". The panel ranks features by:"]},{kind:"math",latex:"\\text{contribution}_j \\;=\\; |\\, W_{\\ell^\\ast, 0, j} \\cdot x_j \\,|"},{kind:"p",parts:["and shows the top 5 as horizontal bars with their real names (",ys("plat1.dx"),", ",ys("gap2.w"),", etc.). Bar color is the ladder's color when the signed contribution is positive, rose when negative. This is the interpretability payoff: you see ",Bt("in real time")," which pieces of the world are driving the decision the agent just took."]},{kind:"h",text:"7c. MLP mode"},{kind:"p",parts:["Three vertical columns of circles, ",Bt("left to right:")]},{kind:"ul",items:[[Bt("Input layer")," (24 circles) β€” the current observation ",_t("s"),". Radius ∝ ",_t("|s_i|"),". Green = positive, pink = negative."],[Bt("Hidden layer")," (64 circles, 8Γ—8 grid) β€” the tanh activations ",_t("h"),". Same coloring; brightness ∝ ",_t("|h_i|"),"."],[Bt("Output layer")," (4 circles) β€” the action probabilities ",_t("\\pi"),". The argmax glows brightest; its label appears in the top-right corner of the panel."]]},{kind:"p",parts:[Bt("Edges")," between layers are rendered with opacity proportional to the instantaneous contribution of one neuron to the next:"]},{kind:"math",latex:"\\alpha(i, j) \\;=\\; \\operatorname{clip}\\!\\bigl(\\,|\\,a_i \\cdot w_{ij}\\,|,\\; 0,\\; 1\\bigr)"},{kind:"p",parts:["...so the lines you see lit up are the ",Bt("currently active pathways"),": pairs where the source neuron's activation is large ",Bt("and")," the connecting weight is large. Dim edges mean that connection isn't doing much for this particular input."]},{kind:"h",text:"8. What to expect over time"},{kind:"ul",items:[[Bt("Episodes 0–10.")," The policy is ~uniform random. The agent wanders, falls off a lot. Reward per episode hovers near zero or slightly negative."],[Bt("Episodes 10–50.")," The shaping reward ",_t("+0.01 \\cdot \\Delta x"),` biases the policy toward "move right." You'll see rightward momentum build. The reward sparkline starts trending up.`],[Bt("Episodes 50–200.")," The agent learns ",Bt("when")," to jump by associating gap features (dims 16–23) with the jump action. Occasionally it clears the level."],[Bt("Episodes 200+.")," The policy stabilizes. The agent clears most levels reliably on the fixed seed. Change the seed from the footer and it'll take a while to adapt to the new layout β€” the net generalizes, but only moderately on this small an architecture."]]},{kind:"hr"},{kind:"h",text:"Running notes"},{kind:"p",parts:[Bt("Backend"),": TensorFlow.js 4.x with its WebGPU backend. Every matrix multiply in the forward pass (",_t("24 \\to 64"),", ",_t("64 \\to 4"),") and every gradient in the backward pass runs as a WebGPU compute shader. Change the seed + Reset to spawn a fresh network on a fresh level."]},{kind:"p",parts:["Design specs: ",ys("docs/design.md")," (base project), ",ys("docs/cofrnet.md")," (CoFrNet integration)."]},{kind:"p",parts:["CoFrNet paper: Puri, Dhurandhar, Pedapati, Shanmugam, Wei, Varshney, ",Bt("CoFrNets: Interpretable Neural Architecture Inspired by Continued Fractions"),", NeurIPS 2021 β€” ",ys("arxiv.org/abs/2506.05586"),"."]},{kind:"p",parts:["Press ",ys("Esc")," or click outside to close."]}];async function _1e(){if(typeof navigator<"u"&&navigator.gpu&&await navigator.gpu.requestAdapter().catch(()=>null))try{if(await Ag("webgpu"),await Rg(),rx()==="webgpu")return"webgpu"}catch{}try{if(await Ag("webgl"),await Rg(),rx()==="webgl")return"webgl"}catch{}return await Ag("cpu"),await Rg(),"cpu"}function qS(n){return n==="mlp"?new _me:new Tme}function T1e(n){const e=window.self!==window.top,t=document.createElement("div");Object.assign(t.style,{position:"fixed",top:"0",left:"0",right:"0",zIndex:"200",background:"#2a1a00",color:"#ffcc66",padding:"8px 16px",font:"13px ui-monospace, Menlo, Consolas, monospace",textAlign:"center",borderBottom:"1px solid #664400"});const s=n==="cpu"?"Running on CPU (very slow).":"Running on WebGL (slower than WebGPU).";if(e){const r=document.createElement("a");r.href=window.location.href,r.target="_blank",r.rel="noopener",r.textContent="Open in a new tab",r.style.color="#66ccff",r.style.textDecoration="underline",t.append(s+" ",r," for full-speed WebGPU.")}else t.textContent=s+" Your browser may not support WebGPU.";document.body.appendChild(t)}async function I1e(){const n=await _1e();console.log(`neuron-runner: using backend=${n} (tf reports: ${rx()})`);const e=document.getElementById("viz-panel"),t=document.getElementById("game-panel"),s=document.getElementById("stats-footer");n!=="webgpu"&&T1e(n);let r=1,i=!1,o=0,a="cofrnet",l=0;const c=new aue;c.reset(r);let u=qS(a),h=new hS(u);const d=new Cme(t),p=new Ime(e),f=new Ame(e),x=new Rme(s,{onReset:b=>{r=b|0,c.reset(r),h.startEpisode(),o=0},onTogglePause:()=>(i=!i,i),onArchChange:b=>{b!==a&&(a=b,h.startEpisode(),u.dispose(),u=qS(a),h=new hS(u),c.reset(r),o=0,l=0,h.startEpisode())}});new w1e,h.startEpisode();let m=0;const g=n==="webgpu"?1:4;function v(){m+=1;const b=m%g===0;if(!i){const y=c.obs(),w=Zs(y),C=u.forward(w),_=C.probs.dataSync(),k=Math.random();let R=0,T=0;for(let L=0;L<_.length;L++)if(R+=_[L],k<R){T=L;break}h.record(w,T);const E=c.step(T);if(h.completeReward(E.reward),l+=E.reward,b){if(a==="mlp"&&C.activations.kind==="mlp"){f.setVisible(!1),p.setVisible(!0);const L=C.activations.hidden.dataSync(),B=u,M=B.w1.dataSync(),P=B.w2.dataSync();p.draw({obs:y,hidden:L,probs:_,action:T,w1:M,w2:P})}else if(a==="cofrnet"&&C.activations.kind==="cofrnet"){p.setVisible(!1),f.setVisible(!0);const L=C.activations.A.dataSync(),B=C.activations.V.dataSync(),P=u.W.dataSync();let F=0,q=-1/0;for(let U=0;U<_.length;U++)_[U]>q&&(q=_[U],F=U);f.draw({obs:y,A:L,V:B,W:P,probs:_,argmax:F})}}d.draw(c),E.done&&(h.endEpisode(),x.pushEpisodeReward(l),x.pushLoss(h.lastLoss),o+=1,l=0,c.reset(r+o),h.startEpisode()),Nt([w,C.probs]),C.activations.kind==="mlp"?Nt([C.activations.hidden]):Nt([C.activations.A,C.activations.V])}m%15===0&&(x.setStatus(`backend=${n} | arch=${a} | episode=${o} | tensors=${sx().numTensors}`),x.draw()),requestAnimationFrame(v)}requestAnimationFrame(v)}I1e().catch(n=>{console.error(n);const e=document.getElementById("stats-footer");e&&(e.textContent=`ERROR: ${String(n)}`)});
index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>neuron-runner</title>
7
- <script type="module" crossorigin src="/assets/index-g3y8_Tn1.js"></script>
8
  <link rel="stylesheet" crossorigin href="/assets/index-Wcl8nPQ0.css">
9
  </head>
10
  <body>
 
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>neuron-runner</title>
7
+ <script type="module" crossorigin src="/assets/index-BQEvJOi8.js"></script>
8
  <link rel="stylesheet" crossorigin href="/assets/index-Wcl8nPQ0.css">
9
  </head>
10
  <body>