Upload index.html
Browse files- index.html +347 -124
index.html
CHANGED
|
@@ -1281,12 +1281,17 @@
|
|
| 1281 |
if (!this.gl) throw Error("No WebGL");
|
| 1282 |
|
| 1283 |
this.isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && this.gl instanceof WebGL2RenderingContext;
|
|
|
|
|
|
|
| 1284 |
if (!this.isWebGL2) {
|
| 1285 |
if (!this.gl.getExtension('OES_texture_float')) throw Error('Need OES_texture_float');
|
| 1286 |
if (!this.gl.getExtension('WEBGL_color_buffer_float')) throw Error('Need WEBGL_color_buffer_float for float render targets');
|
| 1287 |
-
} else
|
| 1288 |
-
|
|
|
|
|
|
|
| 1289 |
}
|
|
|
|
| 1290 |
|
| 1291 |
this.devValidatePrograms = false;
|
| 1292 |
this.wasmKernels = null;
|
|
@@ -1401,8 +1406,8 @@
|
|
| 1401 |
await new Promise(resolve=>setTimeout(resolve,0));
|
| 1402 |
}
|
| 1403 |
}
|
| 1404 |
-
_getTexturePoolKey(w,h){return `${w}x${h}`;}
|
| 1405 |
-
_getPooledTexture(w,h){const key=this._getTexturePoolKey(w,h);const bucket=this.texturePool.get(key);if(bucket&&bucket.length>0){return bucket.pop();}return null;}
|
| 1406 |
getUniformLocationCached(program,name){if(!program)return null;if(!program.uniformLocations)program.uniformLocations=new Map();if(program.uniformLocations.has(name))return program.uniformLocations.get(name);const location=this.gl.getUniformLocation(program,name);program.uniformLocations.set(name,location);return location;}
|
| 1407 |
warmProgram(fragmentShaderSource){const vs2=`#version 300 es\nprecision highp float;in vec2 a_position;void main(){gl_Position=vec4(a_position,0.,1.);}`;const vs1=`precision highp float;attribute vec2 a_position;void main(){gl_Position=vec4(a_position,0.,1.);}`;return this._createProgram(this.isWebGL2?vs2:vs1,fragmentShaderSource);}
|
| 1408 |
_createShader(t,s){const sh=this.gl.createShader(t);this.gl.shaderSource(sh,s);this.gl.compileShader(sh);if(!this.gl.getShaderParameter(sh,this.gl.COMPILE_STATUS)){const e=`Shader Err: ${this.gl.getShaderInfoLog(sh)}\nSrc:\n${s}`;this.gl.deleteShader(sh);throw Error(e);}return sh;}
|
|
@@ -1434,20 +1439,22 @@
|
|
| 1434 |
this.programs.set(fs,p);
|
| 1435 |
return p;
|
| 1436 |
}
|
| 1437 |
-
createTexture(w,h,d=null){
|
| 1438 |
GMLValidate.integer(w,'texture width',{min:1,max:this.maxTextureSize});
|
| 1439 |
GMLValidate.integer(h,'texture height',{min:1,max:this.maxTextureSize});
|
|
|
|
|
|
|
| 1440 |
if(d!==null){
|
| 1441 |
-
if(!(d instanceof Float32Array)) throw new GMLValidationError('Texture data must be
|
| 1442 |
-
if(d.length!==w*h*4) throw new GMLValidationError(`Texture upload requires ${w*h*4}
|
| 1443 |
GMLValidate.data(d,d.length,'texture data');
|
| 1444 |
}
|
| 1445 |
const gl=this.gl;
|
| 1446 |
-
let t=this._getPooledTexture(w,h);
|
| 1447 |
const reused=Boolean(t);
|
| 1448 |
if(!t){t=gl.createTexture();t.__id=textureIdCounter++;}
|
| 1449 |
gl.bindTexture(gl.TEXTURE_2D,t);
|
| 1450 |
-
const iF=this.isWebGL2?gl.RGBA32F:gl.RGBA;
|
| 1451 |
if(!reused){
|
| 1452 |
gl.texImage2D(gl.TEXTURE_2D,0,iF,w,h,0,gl.RGBA,gl.FLOAT,d);
|
| 1453 |
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST);
|
|
@@ -1459,7 +1466,7 @@
|
|
| 1459 |
gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w,h,gl.RGBA,gl.FLOAT,d);
|
| 1460 |
}
|
| 1461 |
gl.bindTexture(gl.TEXTURE_2D,null);
|
| 1462 |
-
t.__meta={width:w,height:h};
|
| 1463 |
t.__refs=1;
|
| 1464 |
return t;
|
| 1465 |
}
|
|
@@ -1649,7 +1656,7 @@
|
|
| 1649 |
texture.__refs=0;
|
| 1650 |
const meta=texture.__meta;
|
| 1651 |
if(meta&&meta.width>0&&meta.height>0){
|
| 1652 |
-
const key=this._getTexturePoolKey(meta.width,meta.height),bucket=this.texturePool.get(key)||[];
|
| 1653 |
if(bucket.length<this.maxTexturePoolPerBucket){bucket.push(texture);this.texturePool.set(key,bucket);return;}
|
| 1654 |
}
|
| 1655 |
if(texture.__meta)delete texture.__meta;
|
|
@@ -1734,8 +1741,8 @@
|
|
| 1734 |
},
|
| 1735 |
|
| 1736 |
data(data, expectedSize, label = 'Tensor data') {
|
| 1737 |
-
if (!(data instanceof Float32Array) && !Array.isArray(data)) {
|
| 1738 |
-
this.fail(`${label} must be an Array or
|
| 1739 |
}
|
| 1740 |
if (data.length !== expectedSize) {
|
| 1741 |
this.fail(`${label} has ${data.length} values but the shape requires ${expectedSize}.`, 'Make the data rectangular or fix the explicit shape.');
|
|
@@ -1817,6 +1824,94 @@
|
|
| 1817 |
}
|
| 1818 |
};
|
| 1819 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1820 |
// --- Tensor Class ---
|
| 1821 |
let tensorIdCounter=0; let textureIdCounter=0; class Tensor{
|
| 1822 |
constructor(data,shape,requires_grad=false,name=null,webglRunner=null,options={}){
|
|
@@ -1833,14 +1928,17 @@
|
|
| 1833 |
this.size=shapeInfo.size;
|
| 1834 |
this.name=name||`t${this.id}`;
|
| 1835 |
this._destroyed=false;
|
| 1836 |
-
this.__gmlProperties=new Set(['shape','size','requires_grad','grad','name']);
|
| 1837 |
-
this.__gmlMethods=new Set(['backward','relu','relu6','leaky_relu','elu','selu','gelu','silu','swish','softplus','mish','sigmoid','tanh','softmax','log_softmax','layer_norm','rms_norm','l2_norm','rope','causal_mask','window_mask','shift','causal_conv1d','affine_scan','repeat_interleave','patchify','frame','slice','concat','dropout','sum','mean','square','abs','exp','log','sqrt','transpose','reshape','permute','batched_matmul','zero_grad']);
|
|
|
|
|
|
|
|
|
|
| 1838 |
this.data=null;
|
| 1839 |
this.texture=null;
|
| 1840 |
this.texWidth=0;
|
| 1841 |
this.texHeight=0;
|
| 1842 |
this.rowAligned=false;
|
| 1843 |
-
this.requires_grad=requires_grad;
|
| 1844 |
this._grad=null;
|
| 1845 |
this._grad_fn=null;
|
| 1846 |
this._ctx=null;
|
|
@@ -1848,20 +1946,18 @@
|
|
| 1848 |
this._materializing=false;
|
| 1849 |
this._deferAllocation=Boolean(options?.deferAllocation);
|
| 1850 |
if(!this.runner&&requires_grad)console.warn(`Tensor ${this.name} requires_grad but no runner.`);
|
| 1851 |
-
if(data instanceof Float32Array){
|
| 1852 |
if(data.length!==this.size)throw Error(`Data size ${data.length} mismatch with shape product ${this.size} for ${this.name}`);
|
| 1853 |
-
|
| 1854 |
-
|
| 1855 |
-
|
| 1856 |
-
this.data=new Float32Array(data);
|
| 1857 |
-
}else if(data===null&&this.runner&&!this._deferAllocation){
|
| 1858 |
this._allocateGPUTexture();
|
| 1859 |
}else if(data!==null){
|
| 1860 |
-
throw Error(`Invalid data type for ${this.name}. Expected Float32Array,
|
| 1861 |
}
|
| 1862 |
-
if(this.data&&this.runner){
|
| 1863 |
this.toGPU();
|
| 1864 |
-
}else if(!this.data&&!this.texture&&this.runner&&!this._deferAllocation){
|
| 1865 |
this._allocateGPUTexture();
|
| 1866 |
}
|
| 1867 |
}
|
|
@@ -1910,7 +2006,8 @@
|
|
| 1910 |
if(this.size===0){this.texWidth=0;this.texHeight=0;if(this.texture)this.runner.deleteTexture(this.texture);this.texture=null;return;}
|
| 1911 |
const{width,height,rowAligned=false}=this.runner._getTextureSizeForShape(this.shape);
|
| 1912 |
if(this.texture&&(this.texWidth!==width||this.texHeight!==height)){this.runner.deleteTexture(this.texture);this.texture=null;}
|
| 1913 |
-
if(!this.texture){this.texture=this.runner.createTexture(width,height,null);}
|
|
|
|
| 1914 |
this.texWidth=width;
|
| 1915 |
this.texHeight=height;
|
| 1916 |
this.rowAligned=Boolean(rowAligned);
|
|
@@ -1922,7 +2019,8 @@
|
|
| 1922 |
if(!this.data){if(!this.texture)this._allocateGPUTexture();return;}
|
| 1923 |
if(this.size===0)return;
|
| 1924 |
if(!this.texture)this._allocateGPUTexture();
|
| 1925 |
-
const
|
|
|
|
| 1926 |
const gl=this.runner.gl;
|
| 1927 |
gl.bindTexture(gl.TEXTURE_2D,this.texture);
|
| 1928 |
gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,this.texWidth,this.texHeight,gl.RGBA,gl.FLOAT,p);
|
|
@@ -1932,18 +2030,40 @@
|
|
| 1932 |
this._assertUsable('toCPU()');
|
| 1933 |
if(this._lazyPlan){this.ensureMaterialized();}
|
| 1934 |
if(!this.runner||!this.texture)return this.data;
|
| 1935 |
-
if(this.data instanceof Float32Array)return this.data;
|
| 1936 |
-
if(this.texWidth===0||this.texHeight===0)return new Float32Array(0);
|
| 1937 |
-
|
| 1938 |
-
if(
|
|
|
|
| 1939 |
return this.data;
|
| 1940 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1941 |
static viewOf(source,newShape,name=null){
|
| 1942 |
GMLValidate.tensor(source,'view source',{runner:source.runner,requireData:true});
|
| 1943 |
const info=GMLValidate.shape(newShape,'view shape');
|
| 1944 |
if(info.size!==source.size)throw new GMLValidationError(`View size mismatch: [${source.shape}] has ${source.size} values but [${newShape}] requires ${info.size}.`);
|
| 1945 |
if(source._lazyPlan)source.ensureMaterialized();
|
| 1946 |
-
const view=new Tensor(null,newShape,false,name||`${source.name}_view`,source.runner,{deferAllocation:true});
|
| 1947 |
if(source.texture&&source.runner){
|
| 1948 |
view.texture=source.runner.retainTexture(source.texture);
|
| 1949 |
view.texWidth=source.texWidth;view.texHeight=source.texHeight;
|
|
@@ -1964,7 +2084,7 @@
|
|
| 1964 |
else if (this._grad_fn) gradStatus = "Pending (Intermediate)";
|
| 1965 |
else gradStatus = "Yes (Leaf)";
|
| 1966 |
}
|
| 1967 |
-
return `Tensor(${this.name},
|
| 1968 |
}
|
| 1969 |
async backward(gradient=null){this._assertUsable('backward()');if(this._lazyPlan)this.ensureMaterialized();if(!this.requires_grad){console.warn(`Cannot call backward() on ${this.name} because requires_grad is false.`);return;}if(!this.runner)throw Error(`No WebGL runner available for backward pass of ${this.name}.`);const visited=new Set();const nodes=[];function build(n){if(!n||visited.has(n.id)||!(n instanceof Tensor))return;visited.add(n.id);if(n._ctx&&n._ctx.inputs)n._ctx.inputs.forEach(i=>build(i));nodes.push(n);}build(this);if(this._grad&&this._grad!==gradient&&gradient !== null){console.warn(`Tensor ${this.name} already has a gradient. It will be overwritten by the new gradient in backward().`);this._grad.destroy();this._grad=null;}if(!this._grad || gradient !== null){ if(gradient){if(!(gradient instanceof Tensor))throw Error("Gradient passed to backward() must be a Tensor.");if(gradient.shape.toString()!==this.shape.toString())throw Error(`Gradient shape mismatch for ${this.name}: expected ${this.shape}, got ${gradient.shape}.`);this._grad=gradient;if(!this._grad.texture && this._grad.size > 0)this._grad.toGPU();}else{if(this.size!==1)console.warn(`Implicit gradient of 1.0 created for non-scalar Tensor ${this.name} (shape [${this.shape}]) in backward().`);const ones=new Float32Array(this.size).fill(1.0);this._grad=new Tensor(ones,this.shape,false,`${this.name}_grad_implicit`,this.runner);if(this._grad.size > 0) this._grad.toGPU();}this._grad.requires_grad=false;} const yieldEveryNodes=4; for(let i=nodes.length-1;i>=0;i--){if(((nodes.length-1-i)%yieldEveryNodes)===0)await GMLUiScheduler.checkpoint();const node=nodes[i];if(node._grad_fn){if(node._grad){if(node._grad._lazyPlan)node._grad.ensureMaterialized();if(!node._grad.texture&&node._grad.size>0)node._grad.toGPU();const gradBeforeBackward=node._grad;const maybePromise=node._grad_fn(node._ctx);if(maybePromise&&typeof maybePromise.then==='function')await maybePromise;if(node!==this&&node._grad===gradBeforeBackward){gradBeforeBackward.destroy();node._grad=null;}}else if(node.requires_grad){console.warn(` Node ${node.name} requires grad but has no gradient computed during backward pass.`);}}else if(node.requires_grad&&!node._grad&&node!==this){console.warn(` Leaf Tensor ${node.name} requires grad but has no gradient after backward pass (and is not the initial Tensor).`);}}}
|
| 1970 |
add(other){if(!(other instanceof Tensor))throw Error("Addition requires both operands to be Tensors.");return Add.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
|
@@ -1973,7 +2093,7 @@
|
|
| 1973 |
div(other){return Div.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
| 1974 |
square(){ return Square.apply({runner:this.runner, inputs:[this]}, this); }
|
| 1975 |
transpose(){return Transpose.apply({runner:this.runner, inputs:[this]}, this);}
|
| 1976 |
-
matmul(other){if(!(other instanceof Tensor))throw Error("Matrix multiplication requires both operands to be Tensors.");return MatMul.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
| 1977 |
batched_matmul(other,transposeA=false,transposeB=false){if(!(other instanceof Tensor))throw Error('batched_matmul requires a Tensor.');return BatchedMatMul.apply({runner:this.runner,inputs:[this,other],disableFusion:true},this,other,transposeA,transposeB);}
|
| 1978 |
permute(axes){return Permute.apply({runner:this.runner,inputs:[this],disableFusion:true},this,axes);}
|
| 1979 |
relu(){ return ReLU.apply({runner:this.runner, inputs:[this]}, this); }
|
|
@@ -2053,6 +2173,13 @@
|
|
| 2053 |
}
|
| 2054 |
}
|
| 2055 |
const output_req_grad=tensorsIn.some(t=>t instanceof Tensor && t.requires_grad);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2056 |
if (!output_req_grad && runner.fusionCompiler && !ctx.disableFusion) {
|
| 2057 |
const fusedOutput = runner.fusionCompiler.tryCreateTensor(this.name, inputs, ctx);
|
| 2058 |
if (fusedOutput) {
|
|
@@ -2060,7 +2187,10 @@
|
|
| 2060 |
}
|
| 2061 |
}
|
| 2062 |
for(const input of tensorsIn){if(input instanceof Tensor && !input.texture && input.size>0)input.toGPU();}
|
| 2063 |
-
const
|
|
|
|
|
|
|
|
|
|
| 2064 |
if(!(output instanceof Tensor)) {
|
| 2065 |
throw new Error(`${this.name}.forward must return a Tensor (returned type: ${typeof output})`);
|
| 2066 |
}
|
|
@@ -4059,6 +4189,55 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4059 |
}
|
| 4060 |
}
|
| 4061 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4062 |
class GMLModel {
|
| 4063 |
constructor(layerSizes, activation, runner, parserRef, seed = 1337) {
|
| 4064 |
GMLValidate.shape(layerSizes, 'mlp layer sizes', { maxRank: 64 });
|
|
@@ -4088,8 +4267,9 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4088 |
this._adamState = new Map();
|
| 4089 |
this._adamStep = 0;
|
| 4090 |
this._scheduler = new GMLCooperativeScheduler(8);
|
| 4091 |
-
this.
|
| 4092 |
-
this.
|
|
|
|
| 4093 |
|
| 4094 |
let state = (seed >>> 0) || 1;
|
| 4095 |
const random = () => {
|
|
@@ -4120,6 +4300,16 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4120 |
}
|
| 4121 |
|
| 4122 |
parameters() { return this.layers.flatMap(layer => [layer.W, layer.B]); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4123 |
|
| 4124 |
summary() {
|
| 4125 |
const params = this.parameters().reduce((sum, p) => sum + p.size, 0);
|
|
@@ -4141,7 +4331,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4141 |
` tensors: [`
|
| 4142 |
];
|
| 4143 |
this.layers.forEach((layer,index)=>{
|
| 4144 |
-
lines.push(` { weight: Tensor(shape=[${layer.W.shape.join(', ')}], device=${layer.W.
|
| 4145 |
});
|
| 4146 |
lines.push(` ]${this.plan?',':''}`);
|
| 4147 |
if(this.plan)lines.push(` plan: ${JSON.stringify(this.plan.summary())}`);
|
|
@@ -4230,6 +4420,12 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4230 |
GMLValidate.tensor(input, 'model input', { runner: this.runner, requireData: true });
|
| 4231 |
if (input.shape.length !== 2) throw new GMLValidationError(`Model input must be 2D [batch, features], got [${input.shape}].`, `Expected feature width ${this.inputSize}.`);
|
| 4232 |
if (input.shape[1] !== this.inputSize) throw new GMLValidationError(`Model expects ${this.inputSize} input features, got ${input.shape[1]}.`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4233 |
let value = this._preprocess(input);
|
| 4234 |
for (let i = 0; i < this.layers.length; i++) {
|
| 4235 |
const layer = this.layers[i];
|
|
@@ -4620,6 +4816,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4620 |
}
|
| 4621 |
|
| 4622 |
async fit(xValue, yValue, epochs = 100, learningRate = 0.01, batchSize = 64) {
|
|
|
|
| 4623 |
GMLValidate.epochs(epochs); GMLValidate.learningRate(learningRate); GMLValidate.batchSize(batchSize);
|
| 4624 |
const X = this.parser.coerceValueToTensor(xValue, 'fit X');
|
| 4625 |
const Y = this.parser.coerceValueToTensor(yValue, 'fit Y');
|
|
@@ -4667,7 +4864,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4667 |
const tp = this.targetPreprocessor?.enabled ? this.targetPreprocessor : { mean: 0, scale: 1 };
|
| 4668 |
for (let i = 0; i < z.length; i++) outputData[i] = z[i] * tp.scale + tp.mean;
|
| 4669 |
}
|
| 4670 |
-
const result = new Tensor(outputData, logits.shape.slice(), false, 'prediction', this.runner);
|
| 4671 |
this._cleanupGraph(graph, new Set([X, ...params]));
|
| 4672 |
return this.parser.trackTensor(result);
|
| 4673 |
} finally { params.forEach((p, i) => p.requires_grad = oldRequiresGrad[i]); }
|
|
@@ -4867,13 +5064,20 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 4867 |
if(!scope.parser.functions.has(functionName))throw new GMLValidationError(`Function '${functionName}' is not defined.`);
|
| 4868 |
this.scope=scope;this.functionName=functionName;this.task='custom';
|
| 4869 |
this.__gmlProperties=new Set(['task']);
|
| 4870 |
-
this.__gmlMethods=new Set(['predict','parameters','zero_grad','step','summary','explain']);
|
| 4871 |
}
|
| 4872 |
async predict(x){
|
| 4873 |
const input=this.scope.parser.coerceValueToTensor(x,'custom model input');
|
| 4874 |
return await this.scope.run(this.functionName,input);
|
| 4875 |
}
|
| 4876 |
parameters(){return this.scope.parameters();}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4877 |
zero_grad(){this.scope.zero_grad();return this;}
|
| 4878 |
async step(lr=0.001){await this.scope.step(lr);return this;}
|
| 4879 |
summary(){return `CustomModel(${this.scope.name}:${this.functionName}, parameters=${this.parameters().length})`;}
|
|
@@ -5292,7 +5496,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 5292 |
const media=value._media?`, media=${JSON.stringify(value._media)}`:(value._mediaTokens?`, tokens=${JSON.stringify(value._mediaTokens)}`:'');
|
| 5293 |
return `Tensor { shape: [${value.shape.join(', ')}], size: ${value.size}, device: ${device}, requires_grad: ${Boolean(value.requires_grad)}${media}${sample} }`;
|
| 5294 |
}
|
| 5295 |
-
if(value instanceof GMLModel || value instanceof GMLCustomArchitectureModel || value instanceof GMLArchitectureScope || value instanceof GMLMetrics || value instanceof GMLTaskPlan || value instanceof PlotSeries || value instanceof PlotHistory || value instanceof VirtualFileHandle) return value.toString();
|
| 5296 |
if(value===null)return 'null';if(value===undefined)return 'undefined';
|
| 5297 |
if(typeof value==='string')return JSON.stringify(value);if(typeof value==='number')return formatGmlNumber(value);if(typeof value==='boolean'||typeof value==='bigint')return String(value);if(typeof value==='function')return `[Function ${value.name||'anonymous'}]`;
|
| 5298 |
if(Array.isArray(value)){
|
|
@@ -5981,7 +6185,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 5981 |
type: ['Type inspection', 'type(value)', 'Returns a short type name such as int, float, string, array, tensor, or model.'],
|
| 5982 |
tensor: ['Tensor', 'tensor(data)', 'Creates a tensor from numeric data.'],
|
| 5983 |
param: ['Trainable tensor', 'param(data)', 'Creates a trainable tensor.'],
|
| 5984 |
-
learn: ['Learning', 'model = learn(X, Y)', '
|
| 5985 |
plan: ['Learning plan', 'plan(X, Y)', 'Shows the plan GML would use for a learning task.'],
|
| 5986 |
classify: ['Classification', 'classify(model, X, labels?)', 'Returns predicted class IDs or label names.'],
|
| 5987 |
predict: ['Prediction', 'predict(model, X)', 'Runs a model and returns its outputs.'],
|
|
@@ -5996,6 +6200,14 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 5996 |
fit: ['Training', 'fit(model, X, Y, ...)', 'Trains an explicitly created model.'],
|
| 5997 |
matmul: ['Matrix multiplication', 'matmul(A, B)', 'Multiplies two rank-2 tensors.'],
|
| 5998 |
linear: ['Linear layer', 'linear(X, outputSize) · linear(X, W, B)', 'Projects the last feature dimension; architecture contexts can own and reuse parameters automatically.'],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5999 |
sum: ['Sum', 'sum(x, axis?)', 'Adds tensor values, optionally along an axis.'],
|
| 6000 |
mean: ['Mean', 'mean(x, axis?)', 'Averages tensor values, optionally along an axis.'],
|
| 6001 |
relu: ['ReLU', 'relu(x)', 'Applies max(0, x).'],
|
|
@@ -6551,6 +6763,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 6551 |
|
| 6552 |
defineFunction(name, argNames, processedBody) {
|
| 6553 |
if (this.functions.has(name)) console.warn(`Redefining func ${name}`);
|
|
|
|
| 6554 |
this.functions.set(name, { args: argNames, body: processedBody });
|
| 6555 |
this.log(` Defined function ${name}(${argNames.join(', ')})`);
|
| 6556 |
}
|
|
@@ -7084,7 +7297,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7084 |
'int','float','string','bool','type','image','audio','video','patchify','frame',
|
| 7085 |
'tensor','param','zeros','ones','add','sub','mul','div','square','abs','negate','transpose','permute','matmul','batched_matmul','linear',
|
| 7086 |
'relu','relu6','leaky_relu','elu','selu','gelu','silu','swish','swiglu','geglu','softplus','mish','sigmoid','tanh','softmax','log_softmax','layer_norm','rms_norm','l2_norm','rope','causal_mask','window_mask','attention','shift','causal_conv1d','affine_scan','repeat_interleave','concat','slice','one_hot','embedding','lerp','dropout','exp','log','sqrt','sum','mean','normalize','flatten','reshape',
|
| 7087 |
-
'mlp','architecture','plan','learn','fit','predict','evaluate','classify','mse','binary_cross_entropy','bce','cross_entropy',
|
| 7088 |
'assert','assert_shape','assert_finite','describe','series','history','plot','dots','progress',
|
| 7089 |
'watch','write','append','read','files','remove','emit','clear_live','grid','table','md','markdown','graphemes','text_length',
|
| 7090 |
'print','print_grad','has_grad','step','zero_grad'
|
|
@@ -7180,7 +7393,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7180 |
if (evArgs.length !== 1) throw new GMLValidationError('type(value) expects exactly one value.');
|
| 7181 |
const value = evArgs[0];
|
| 7182 |
if (value instanceof Tensor) return 'tensor';
|
| 7183 |
-
if (value instanceof GMLModel || value instanceof GMLCustomArchitectureModel) return 'model';
|
| 7184 |
if (value instanceof GMLArchitectureScope) return 'architecture';
|
| 7185 |
if (value instanceof GMLMetrics) return 'metrics';
|
| 7186 |
if (value instanceof GMLTaskPlan || value?.kind === 'semantic_ml_plan') return 'plan';
|
|
@@ -7272,7 +7485,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7272 |
if (evArgs.length !== 2) throw Error("matmul(a, b)");
|
| 7273 |
const a = tensorArg(evArgs[0], 'matmul arg 1');
|
| 7274 |
const b = tensorArg(evArgs[1], 'matmul arg 2');
|
| 7275 |
-
return this.trackTensor(
|
| 7276 |
}
|
| 7277 |
case 'linear': {
|
| 7278 |
if(evArgs.length===2 && typeof evArgs[1]==='number'){
|
|
@@ -7285,7 +7498,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7285 |
const a=tensorArg(evArgs[0],'linear input');
|
| 7286 |
const w=tensorArg(evArgs[1],'linear weight');
|
| 7287 |
const bias=tensorArg(evArgs[2],'linear bias');
|
| 7288 |
-
if(a.shape.length===2)return this.trackTensor(Linear.apply({runner:this.runner,inputs:[a,w,bias],disableFusion:true,architecture:GMLArchitectureRuntime.current()?.family||null},a,w,bias));
|
| 7289 |
if(a.shape.length===3){const inFeatures=a.shape[2];if(w.shape.length!==2||w.shape[0]!==inFeatures)throw new GMLValidationError(`linear weight must be [${inFeatures}, out_features] for input [${a.shape}].`);const rows=a.size/inFeatures,flat=this.trackTensor(Reshape.apply({runner:this.runner,inputs:[a]},a,[rows,inFeatures])),projected=this.trackTensor(Linear.apply({runner:this.runner,inputs:[flat,w,bias],disableFusion:true,architecture:GMLArchitectureRuntime.current()?.family||null},flat,w,bias));return this.trackTensor(Reshape.apply({runner:this.runner,inputs:[projected]},projected,[a.shape[0],a.shape[1],w.shape[1]]));}
|
| 7290 |
throw new GMLValidationError(`linear() expects rank-2 or rank-3 input, got [${a.shape}].`);
|
| 7291 |
}
|
|
@@ -7411,8 +7624,9 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7411 |
return GMLTaskPlanner.create(X, Y, this.runner, evArgs[2] ?? 'balanced');
|
| 7412 |
}
|
| 7413 |
case 'learn': {
|
|
|
|
| 7414 |
if (evArgs.length < 2 || evArgs.length > 5) {
|
| 7415 |
-
throw new GMLValidationError("learn(X, Y, intent?)", "
|
| 7416 |
}
|
| 7417 |
const rawX = evArgs[0];
|
| 7418 |
const stringEncoded = isGmlStringDataset(rawX);
|
|
@@ -7450,6 +7664,14 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7450 |
await model._fitWithPlan(X, Y, semanticPlan);
|
| 7451 |
return model;
|
| 7452 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7453 |
case 'mse': {
|
| 7454 |
if (evArgs.length !== 2) throw new GMLValidationError("mse(prediction, target)");
|
| 7455 |
const a = tensorArg(evArgs[0], 'mse prediction');
|
|
@@ -7825,6 +8047,19 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7825 |
}
|
| 7826 |
|
| 7827 |
// --- Syntax Highlighting Globals & Functions ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7828 |
const _syntaxHighlightingElements = {
|
| 7829 |
codeInput: null,
|
| 7830 |
highlightingArea: null,
|
|
@@ -7848,7 +8083,7 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7848 |
runBenchmarkButton: null
|
| 7849 |
};
|
| 7850 |
const _appState = {
|
| 7851 |
-
selectedPreset: '
|
| 7852 |
isBusy: false
|
| 7853 |
};
|
| 7854 |
const GML_WORKSPACE_KEY = 'gml.v0.3.workspace';
|
|
@@ -7889,8 +8124,19 @@ precision highp float;precision highp int;precision highp sampler2D;uniform samp
|
|
| 7889 |
function restoreWorkspace() {
|
| 7890 |
const storage=getWorkspaceStorage();if(!storage)return false;
|
| 7891 |
let state;
|
| 7892 |
-
try{
|
| 7893 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7894 |
const input=_syntaxHighlightingElements.codeInput;if(!input)return false;
|
| 7895 |
_workspaceRestoring=true;
|
| 7896 |
try{
|
|
@@ -8047,79 +8293,28 @@ assert_shape(Predictions, 4, 1)`
|
|
| 8047 |
},
|
| 8048 |
showcase: {
|
| 8049 |
label: 'Showcase',
|
| 8050 |
-
description: '
|
| 8051 |
-
code: `
|
| 8052 |
-
|
| 8053 |
-
NumA = 5
|
| 8054 |
-
NumB = 10
|
| 8055 |
-
NumC = 2
|
| 8056 |
-
Result1 = NumA + NumB * NumC
|
| 8057 |
-
Result2 = (NumA + NumB) * NumC
|
| 8058 |
-
print "Result1:", Result1
|
| 8059 |
-
print "Result2:", Result2
|
| 8060 |
-
|
| 8061 |
-
# Tensor ops with inferred shapes
|
| 8062 |
-
T1 = tensor([[1., 2.], [3., 4.]])
|
| 8063 |
-
T2 = tensor([[10., 20.], [30., 40.]])
|
| 8064 |
-
T3 = T1 + T2
|
| 8065 |
-
T4 = T1 * NumA
|
| 8066 |
-
print T3
|
| 8067 |
-
print T4
|
| 8068 |
-
|
| 8069 |
-
# Autograd
|
| 8070 |
-
X = param([1., 2., 3.])
|
| 8071 |
-
Y = X * 2.0
|
| 8072 |
-
Z = sum(Y)
|
| 8073 |
-
Z.backward()
|
| 8074 |
-
print_grad(X)
|
| 8075 |
-
|
| 8076 |
-
# Intent-first ML: task + implementation are inferred
|
| 8077 |
-
Features = [[0.,0.], [0.,1.], [1.,0.], [1.,1.]]
|
| 8078 |
-
Labels = [[0.], [1.], [1.], [1.]]
|
| 8079 |
-
Idea = plan(Features, Labels)
|
| 8080 |
-
print Idea
|
| 8081 |
-
AutoModel = learn(Features, Labels)
|
| 8082 |
-
print AutoModel.explain()
|
| 8083 |
-
print AutoModel.predict(Features)
|
| 8084 |
-
|
| 8085 |
-
# Low-level escape hatch
|
| 8086 |
-
# Manual forward pass
|
| 8087 |
-
function linear(input_tensor, weights, bias) {
|
| 8088 |
-
mm_result = matmul(input_tensor, weights)
|
| 8089 |
-
output = mm_result + bias
|
| 8090 |
-
return output
|
| 8091 |
-
}
|
| 8092 |
|
| 8093 |
-
|
| 8094 |
-
|
| 8095 |
-
return exp_logits / sum(exp_logits)
|
| 8096 |
-
}
|
| 8097 |
|
| 8098 |
-
|
| 8099 |
-
|
|
|
|
| 8100 |
}
|
| 8101 |
|
| 8102 |
-
|
| 8103 |
-
|
| 8104 |
-
|
| 8105 |
-
|
| 8106 |
-
|
| 8107 |
-
|
| 8108 |
-
|
| 8109 |
-
|
| 8110 |
-
|
| 8111 |
-
|
| 8112 |
-
|
| 8113 |
-
# Normalize / reshape
|
| 8114 |
-
Flat = param([1., 2., 3., 4., 5., 6.])
|
| 8115 |
-
Grid = reshape(Flat, 2, 3)
|
| 8116 |
-
NormInput = param([10., 20., 30., 40.])
|
| 8117 |
-
NormOut = normalize(NormInput)
|
| 8118 |
-
NormLoss = sum(NormOut)
|
| 8119 |
-
NormLoss.backward()
|
| 8120 |
-
print Grid
|
| 8121 |
-
print NormOut
|
| 8122 |
-
print_grad(NormInput)`
|
| 8123 |
},
|
| 8124 |
syntax: {
|
| 8125 |
label: 'Syntax Edge Cases',
|
|
@@ -9199,6 +9394,30 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 9199 |
return 'binary_cross_entropy() and cross_entropy() execute forward/backward without GPU→CPU readback.';
|
| 9200 |
}
|
| 9201 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9202 |
{
|
| 9203 |
name:'Public UI Hides Internal Benchmarking',
|
| 9204 |
async run(){if(document.getElementById('runBenchmarkButton')||document.getElementById('benchmarkSummary'))throw new Error('Internal benchmark controls leaked into the user UI.');const visible=document.body.innerText;if(/TensorFlow|TF\.js|release gate/i.test(visible))throw new Error('Internal benchmark/release wording leaked into visible UI.');return 'Internal performance/release tooling remains callable programmatically but is absent from the normal product UI.';}
|
|
@@ -9210,8 +9429,8 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 9210 |
{ type: 'string', regex: /^"(?:\\.|[^"\\])*"/ },
|
| 9211 |
{ type: 'directive', regex: /^@(dev|help|quiet)\b/ },
|
| 9212 |
{ type: 'keyword', regex: /^\b(function|return|if|else|for|while|true|false|null)\b/ }, // Added null
|
| 9213 |
-
{ type: 'builtin', regex: /^\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*(grad|backward|relu|relu6|leaky_relu|elu|selu|gelu|silu|swish|swiglu|geglu|softplus|mish|sigmoid|tanh|softmax|log_softmax|layer_norm|rms_norm|l2_norm|rope|causal_mask|window_mask|shift|causal_conv1d|affine_scan|repeat_interleave|patchify|frame|permute|batched_matmul|slice|concat|dropout|sum|mean|square|abs|exp|log|sqrt|transpose|reshape|zero_grad|fit|predict|evaluate|classify|summary|parameters|explain|run|repeat|model|step|wait|text|animation|anim|on|once|off|emit|when|done|error|clear|start|update|title|json|lines)\b/ },
|
| 9214 |
-
{ type: 'builtin', regex: /^\b(seed|random|int|float|string|bool|type|image|audio|video|patchify|frame|tensor|param|mlp|architecture|plan|learn|fit|predict|evaluate|classify|mse|binary_cross_entropy|bce|cross_entropy|assert|assert_shape|assert_finite|describe|add|sub|mul|div|square|abs|negate|transpose|permute|matmul|batched_matmul|linear|relu|relu6|leaky_relu|elu|selu|gelu|silu|swish|swiglu|geglu|softplus|mish|sigmoid|tanh|softmax|log_softmax|layer_norm|rms_norm|l2_norm|rope|causal_mask|window_mask|attention|shift|causal_conv1d|affine_scan|repeat_interleave|concat|slice|one_hot|embedding|lerp|dropout|exp|log|sqrt|sum|mean|normalize|flatten|reshape|zeros|ones|series|history|plot|dots|progress|watch|emit|clear_live|write|append|read|files|remove|grid|table|md|markdown|graphemes|text_length|step|zero_grad|print|print_grad|has_grad|_clear_grad|_set_grad)\b/ },
|
| 9215 |
{ type: 'number', regex: /^(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+\-]?\d+)?\b/ },
|
| 9216 |
{ type: 'operator', regex: /^(==|<=|>=|!=|&&|\|\|)/ },
|
| 9217 |
{ type: 'operator', regex: /^[+\-*/=<>^]/ },
|
|
@@ -11207,6 +11426,8 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 11207 |
}
|
| 11208 |
|
| 11209 |
function highlightSyntax(code) {
|
|
|
|
|
|
|
| 11210 |
let remainingCode = code;
|
| 11211 |
let htmlOutput = '';
|
| 11212 |
|
|
@@ -11221,7 +11442,9 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 11221 |
if (tokenDef.type === 'whitespace' || tokenDef.type === 'unknown') {
|
| 11222 |
htmlOutput += escapeHtml(tokenValue);
|
| 11223 |
} else {
|
| 11224 |
-
|
|
|
|
|
|
|
| 11225 |
}
|
| 11226 |
matched = true;
|
| 11227 |
break;
|
|
@@ -11580,7 +11803,7 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 11580 |
const caretPos = codeInput.selectionDirection === 'backward' ? codeInput.selectionStart : codeInput.selectionEnd;
|
| 11581 |
|
| 11582 |
let left = paddingLeft;
|
| 11583 |
-
let top = paddingTop +
|
| 11584 |
let width = caretSize;
|
| 11585 |
let height = caretSize;
|
| 11586 |
let borderRadius = '50%';
|
|
@@ -11602,7 +11825,7 @@ Responsive = learn(X, Y, [8], 10, 0.03)`);
|
|
| 11602 |
|
| 11603 |
const atLineEnd = caretPos === codeInput.value.length || codeInput.value.charAt(caretPos) === '\n';
|
| 11604 |
if (atLineEnd) {
|
| 11605 |
-
top +=
|
| 11606 |
} else {
|
| 11607 |
width = 2;
|
| 11608 |
height = Math.max(14, Math.round(lineHeight * 0.95) - 2);
|
|
|
|
| 1281 |
if (!this.gl) throw Error("No WebGL");
|
| 1282 |
|
| 1283 |
this.isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && this.gl instanceof WebGL2RenderingContext;
|
| 1284 |
+
this.colorBufferFloatExt = null;
|
| 1285 |
+
this.colorBufferHalfFloatExt = null;
|
| 1286 |
if (!this.isWebGL2) {
|
| 1287 |
if (!this.gl.getExtension('OES_texture_float')) throw Error('Need OES_texture_float');
|
| 1288 |
if (!this.gl.getExtension('WEBGL_color_buffer_float')) throw Error('Need WEBGL_color_buffer_float for float render targets');
|
| 1289 |
+
} else {
|
| 1290 |
+
this.colorBufferFloatExt = this.gl.getExtension('EXT_color_buffer_float');
|
| 1291 |
+
this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float');
|
| 1292 |
+
if (!this.colorBufferFloatExt && !this.colorBufferHalfFloatExt) console.warn('No floating-point color-buffer extension');
|
| 1293 |
}
|
| 1294 |
+
this.supportsF16RenderTarget = Boolean(this.isWebGL2 && (this.colorBufferFloatExt || this.colorBufferHalfFloatExt));
|
| 1295 |
|
| 1296 |
this.devValidatePrograms = false;
|
| 1297 |
this.wasmKernels = null;
|
|
|
|
| 1406 |
await new Promise(resolve=>setTimeout(resolve,0));
|
| 1407 |
}
|
| 1408 |
}
|
| 1409 |
+
_getTexturePoolKey(w,h,dtype='f32'){return `${w}x${h}:${dtype}`;}
|
| 1410 |
+
_getPooledTexture(w,h,dtype='f32'){const key=this._getTexturePoolKey(w,h,dtype);const bucket=this.texturePool.get(key);if(bucket&&bucket.length>0){return bucket.pop();}return null;}
|
| 1411 |
getUniformLocationCached(program,name){if(!program)return null;if(!program.uniformLocations)program.uniformLocations=new Map();if(program.uniformLocations.has(name))return program.uniformLocations.get(name);const location=this.gl.getUniformLocation(program,name);program.uniformLocations.set(name,location);return location;}
|
| 1412 |
warmProgram(fragmentShaderSource){const vs2=`#version 300 es\nprecision highp float;in vec2 a_position;void main(){gl_Position=vec4(a_position,0.,1.);}`;const vs1=`precision highp float;attribute vec2 a_position;void main(){gl_Position=vec4(a_position,0.,1.);}`;return this._createProgram(this.isWebGL2?vs2:vs1,fragmentShaderSource);}
|
| 1413 |
_createShader(t,s){const sh=this.gl.createShader(t);this.gl.shaderSource(sh,s);this.gl.compileShader(sh);if(!this.gl.getShaderParameter(sh,this.gl.COMPILE_STATUS)){const e=`Shader Err: ${this.gl.getShaderInfoLog(sh)}\nSrc:\n${s}`;this.gl.deleteShader(sh);throw Error(e);}return sh;}
|
|
|
|
| 1439 |
this.programs.set(fs,p);
|
| 1440 |
return p;
|
| 1441 |
}
|
| 1442 |
+
createTexture(w,h,d=null,dtype='f32'){
|
| 1443 |
GMLValidate.integer(w,'texture width',{min:1,max:this.maxTextureSize});
|
| 1444 |
GMLValidate.integer(h,'texture height',{min:1,max:this.maxTextureSize});
|
| 1445 |
+
dtype=GMLDType.normalize(dtype);
|
| 1446 |
+
const storageDtype=(dtype==='f16'&&this.supportsF16RenderTarget)?'f16':'f32';
|
| 1447 |
if(d!==null){
|
| 1448 |
+
if(!(d instanceof Float32Array)) throw new GMLValidationError('Texture upload data must be Float32Array or null.');
|
| 1449 |
+
if(d.length!==w*h*4) throw new GMLValidationError(`Texture upload requires ${w*h*4} values, got ${d.length}.`);
|
| 1450 |
GMLValidate.data(d,d.length,'texture data');
|
| 1451 |
}
|
| 1452 |
const gl=this.gl;
|
| 1453 |
+
let t=this._getPooledTexture(w,h,storageDtype);
|
| 1454 |
const reused=Boolean(t);
|
| 1455 |
if(!t){t=gl.createTexture();t.__id=textureIdCounter++;}
|
| 1456 |
gl.bindTexture(gl.TEXTURE_2D,t);
|
| 1457 |
+
const iF=this.isWebGL2?(storageDtype==='f16'?gl.RGBA16F:gl.RGBA32F):gl.RGBA;
|
| 1458 |
if(!reused){
|
| 1459 |
gl.texImage2D(gl.TEXTURE_2D,0,iF,w,h,0,gl.RGBA,gl.FLOAT,d);
|
| 1460 |
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST);
|
|
|
|
| 1466 |
gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w,h,gl.RGBA,gl.FLOAT,d);
|
| 1467 |
}
|
| 1468 |
gl.bindTexture(gl.TEXTURE_2D,null);
|
| 1469 |
+
t.__meta={width:w,height:h,dtype:storageDtype};
|
| 1470 |
t.__refs=1;
|
| 1471 |
return t;
|
| 1472 |
}
|
|
|
|
| 1656 |
texture.__refs=0;
|
| 1657 |
const meta=texture.__meta;
|
| 1658 |
if(meta&&meta.width>0&&meta.height>0){
|
| 1659 |
+
const key=this._getTexturePoolKey(meta.width,meta.height,meta.dtype||'f32'),bucket=this.texturePool.get(key)||[];
|
| 1660 |
if(bucket.length<this.maxTexturePoolPerBucket){bucket.push(texture);this.texturePool.set(key,bucket);return;}
|
| 1661 |
}
|
| 1662 |
if(texture.__meta)delete texture.__meta;
|
|
|
|
| 1741 |
},
|
| 1742 |
|
| 1743 |
data(data, expectedSize, label = 'Tensor data') {
|
| 1744 |
+
if (!(data instanceof Float32Array) && !(data instanceof Int8Array) && !Array.isArray(data)) {
|
| 1745 |
+
this.fail(`${label} must be an Array, Float32Array, or Int8Array. Got ${data === null ? 'null' : typeof data}.`);
|
| 1746 |
}
|
| 1747 |
if (data.length !== expectedSize) {
|
| 1748 |
this.fail(`${label} has ${data.length} values but the shape requires ${expectedSize}.`, 'Make the data rectangular or fix the explicit shape.');
|
|
|
|
| 1824 |
}
|
| 1825 |
};
|
| 1826 |
|
| 1827 |
+
const GMLDType = {
|
| 1828 |
+
names: new Set(['f32','f16','i8']),
|
| 1829 |
+
normalize(value){
|
| 1830 |
+
const v=String(value??'f32').trim().toLowerCase();
|
| 1831 |
+
const aliases={float:'f32',float32:'f32',fp32:'f32',half:'f16',float16:'f16',fp16:'f16',int8:'i8'};
|
| 1832 |
+
const out=aliases[v]||v;
|
| 1833 |
+
if(!this.names.has(out))throw new GMLValidationError(`Unsupported dtype '${value}'.`,`Use f32, f16, or i8.`);
|
| 1834 |
+
return out;
|
| 1835 |
+
},
|
| 1836 |
+
floatToHalfBits(value){
|
| 1837 |
+
const f32=new Float32Array(1),u32=new Uint32Array(f32.buffer);f32[0]=Number(value)||0;const x=u32[0];
|
| 1838 |
+
const sign=(x>>>16)&0x8000,exp=(x>>>23)&0xff,mant=x&0x7fffff;
|
| 1839 |
+
if(exp===0xff)return sign|(mant?0x7e00:0x7c00);
|
| 1840 |
+
let e=exp-127+15;
|
| 1841 |
+
if(e>=31)return sign|0x7c00;
|
| 1842 |
+
if(e<=0){if(e<-10)return sign;let m=(mant|0x800000)>>(1-e);return sign+((m+0x1000)>>13);}
|
| 1843 |
+
return sign|(e<<10)|((mant+0x1000)>>13);
|
| 1844 |
+
},
|
| 1845 |
+
halfBitsToFloat(bits){
|
| 1846 |
+
const sign=(bits&0x8000)?-1:1,exp=(bits>>>10)&31,mant=bits&1023;
|
| 1847 |
+
if(exp===0)return sign*Math.pow(2,-14)*(mant/1024);
|
| 1848 |
+
if(exp===31)return mant?NaN:sign*Infinity;
|
| 1849 |
+
return sign*Math.pow(2,exp-15)*(1+mant/1024);
|
| 1850 |
+
},
|
| 1851 |
+
roundF16(value){return this.halfBitsToFloat(this.floatToHalfBits(value));},
|
| 1852 |
+
quantizeI8(values){
|
| 1853 |
+
let maxAbs=0;for(let i=0;i<values.length;i++)maxAbs=Math.max(maxAbs,Math.abs(Number(values[i])||0));
|
| 1854 |
+
const scale=maxAbs>0?maxAbs/127:1,q=new Int8Array(values.length);
|
| 1855 |
+
for(let i=0;i<values.length;i++)q[i]=Math.max(-127,Math.min(127,Math.round((Number(values[i])||0)/scale)));
|
| 1856 |
+
return {data:q,scale,zeroPoint:0};
|
| 1857 |
+
},
|
| 1858 |
+
toFloat32(data,dtype='f32',quantization=null){
|
| 1859 |
+
dtype=this.normalize(dtype);
|
| 1860 |
+
if(data instanceof Float32Array&&dtype!=='i8')return new Float32Array(data);
|
| 1861 |
+
const out=new Float32Array(data?.length||0);
|
| 1862 |
+
if(dtype==='i8'){
|
| 1863 |
+
const scale=quantization?.scale??1,zero=quantization?.zeroPoint??0;
|
| 1864 |
+
for(let i=0;i<out.length;i++)out[i]=(Number(data[i])-zero)*scale;
|
| 1865 |
+
}else for(let i=0;i<out.length;i++)out[i]=Number(data[i]);
|
| 1866 |
+
return out;
|
| 1867 |
+
},
|
| 1868 |
+
cast(data,fromDtype,toDtype,quantization=null){
|
| 1869 |
+
toDtype=this.normalize(toDtype);const f=this.toFloat32(data,fromDtype,quantization);
|
| 1870 |
+
if(toDtype==='f32')return {data:f,quantization:null};
|
| 1871 |
+
if(toDtype==='f16'){for(let i=0;i<f.length;i++)f[i]=this.roundF16(f[i]);return {data:f,quantization:null};}
|
| 1872 |
+
const q=this.quantizeI8(f);return {data:q.data,quantization:{scale:q.scale,zeroPoint:q.zeroPoint}};
|
| 1873 |
+
},
|
| 1874 |
+
promote(tensors){
|
| 1875 |
+
const ds=tensors.map(t=>t?.dtype||'f32');
|
| 1876 |
+
if(ds.includes('f32')||ds.includes('i8'))return 'f32';
|
| 1877 |
+
return ds.length&&ds.every(d=>d==='f16')?'f16':'f32';
|
| 1878 |
+
}
|
| 1879 |
+
};
|
| 1880 |
+
|
| 1881 |
+
const GMLCpu = {
|
| 1882 |
+
matmul(a,b,bias=null){
|
| 1883 |
+
if(a.shape.length!==2||b.shape.length!==2||a.shape[1]!==b.shape[0])throw new GMLValidationError(`CPU matmul shape mismatch [${a.shape}] @ [${b.shape}].`);
|
| 1884 |
+
const A=a.numericData(),B=b.numericData(),M=a.shape[0],K=a.shape[1],N=b.shape[1],out=new Float32Array(M*N),bv=bias?bias.numericData():null;
|
| 1885 |
+
for(let m=0;m<M;m++)for(let n=0;n<N;n++){let acc=bv?bv[n]:0;for(let k=0;k<K;k++)acc+=A[m*K+k]*B[k*N+n];out[m*N+n]=acc;}
|
| 1886 |
+
return new Tensor(out,[M,N],false,bias?'cpu_linear':'cpu_matmul',a.runner,{device:'cpu',dtype:GMLDType.promote([a,b])});
|
| 1887 |
+
},
|
| 1888 |
+
activate(t,kind){
|
| 1889 |
+
const x=t.numericData(),out=new Float32Array(x.length);
|
| 1890 |
+
for(let i=0;i<x.length;i++){const v=x[i];switch(kind){case'relu':out[i]=Math.max(0,v);break;case'relu6':out[i]=Math.min(6,Math.max(0,v));break;case'gelu':out[i]=v>10?v:(v<-10?0:0.5*v*(1+Math.tanh(0.7978845608*(v+0.044715*v*v*v))));break;case'silu':case'swish':out[i]=v/(1+Math.exp(-v));break;case'tanh':out[i]=Math.tanh(v);break;case'sigmoid':out[i]=v>=0?1/(1+Math.exp(-v)):Math.exp(v)/(1+Math.exp(v));break;case'exp':out[i]=Math.exp(v);break;case'log':out[i]=Math.log(v);break;case'sqrt':out[i]=Math.sqrt(v);break;case'abs':out[i]=Math.abs(v);break;case'square':out[i]=v*v;break;case'negate':out[i]=-v;break;default:out[i]=v;}}
|
| 1891 |
+
return new Tensor(out,t.shape.slice(),false,`cpu_${kind}`,t.runner,{device:'cpu',dtype:t.dtype==='f16'?'f16':'f32'});
|
| 1892 |
+
},
|
| 1893 |
+
elementwise(name,a,b){
|
| 1894 |
+
const A=a.numericData(), B=b instanceof Tensor?b.numericData():null, out=new Float32Array(A.length), scalar=typeof b==='number'?b:null;
|
| 1895 |
+
const same=B&&B.length===A.length, trailing=B&&a.shape.length===2&&((b.shape.length===1&&b.shape[0]===a.shape[1])||(b.shape.length===2&&b.shape[0]===1&&b.shape[1]===a.shape[1]));
|
| 1896 |
+
if(B&&!same&&!trailing&&!((B.length===1)))throw new GMLValidationError(`CPU ${name} does not support broadcast [${a.shape}] with [${b.shape}].`,`Move tensors to GPU for broader broadcast support.`);
|
| 1897 |
+
const cols=a.shape.length===2?a.shape[1]:A.length;
|
| 1898 |
+
for(let i=0;i<A.length;i++){const y=scalar!==null?scalar:(B.length===1?B[0]:(trailing?B[i%cols]:B[i]));switch(name){case'Add':out[i]=A[i]+y;break;case'Sub':out[i]=A[i]-y;break;case'Mul':out[i]=A[i]*y;break;case'Div':out[i]=A[i]/y;break;}}
|
| 1899 |
+
const dtype=GMLDType.promote([a,b instanceof Tensor?b:null].filter(Boolean));return new Tensor(out,a.shape.slice(),false,`cpu_${name.toLowerCase()}`,a.runner,{device:'cpu',dtype});
|
| 1900 |
+
},
|
| 1901 |
+
reduce(t,mean=false){const x=t.numericData();let acc=0;for(let i=0;i<x.length;i++)acc+=x[i];if(mean&&x.length)acc/=x.length;return new Tensor(new Float32Array([acc]),[1],false,mean?'cpu_mean':'cpu_sum',t.runner,{device:'cpu',dtype:t.dtype==='f16'?'f16':'f32'});},
|
| 1902 |
+
transpose(t){if(t.shape.length!==2)throw new GMLValidationError('CPU transpose currently expects a rank-2 tensor.');const [R,C]=t.shape,x=t.numericData(),out=new Float32Array(x.length);for(let r=0;r<R;r++)for(let c=0;c<C;c++)out[c*R+r]=x[r*C+c];return new Tensor(out,[C,R],false,'cpu_transpose',t.runner,{device:'cpu',dtype:t.dtype});},
|
| 1903 |
+
softmax(t,logMode=false){const width=t.shape[t.shape.length-1],rows=t.size/width,x=t.numericData(),out=new Float32Array(x.length);for(let r=0;r<rows;r++){const base=r*width;let mx=-Infinity;for(let c=0;c<width;c++)mx=Math.max(mx,x[base+c]);let sum=0;for(let c=0;c<width;c++)sum+=Math.exp(x[base+c]-mx);const lz=Math.log(sum)+mx;for(let c=0;c<width;c++)out[base+c]=logMode?x[base+c]-lz:Math.exp(x[base+c]-lz);}return new Tensor(out,t.shape.slice(),false,logMode?'cpu_log_softmax':'cpu_softmax',t.runner,{device:'cpu',dtype:t.dtype==='f16'?'f16':'f32'});},
|
| 1904 |
+
norm(t,rms=false,epsilon=1e-5){const width=t.shape[t.shape.length-1],rows=t.size/width,x=t.numericData(),out=new Float32Array(x.length);for(let r=0;r<rows;r++){const base=r*width;let mean=0,sq=0;for(let c=0;c<width;c++){mean+=x[base+c];sq+=x[base+c]*x[base+c];}mean/=width;sq/=width;const inv=1/Math.sqrt((rms?sq:(sq-mean*mean))+epsilon);for(let c=0;c<width;c++)out[base+c]=(x[base+c]-(rms?0:mean))*inv;}return new Tensor(out,t.shape.slice(),false,rms?'cpu_rms_norm':'cpu_layer_norm',t.runner,{device:'cpu',dtype:t.dtype==='f16'?'f16':'f32'});},
|
| 1905 |
+
apply(opName,inputs,ctx={}){
|
| 1906 |
+
const tensors=inputs.filter(x=>x instanceof Tensor);if(!tensors.length)return null;const a=tensors[0];
|
| 1907 |
+
if(['Add','Sub','Mul','Div'].includes(opName))return this.elementwise(opName,a,inputs[1]);
|
| 1908 |
+
const unary={Square:'square',Negate:'negate',Abs:'abs',Exp:'exp',Log:'log',Sqrt:'sqrt',ReLU:'relu',ReLU6:'relu6',GELU:'gelu',SiLU:'silu',Sigmoid:'sigmoid',Tanh:'tanh'};if(unary[opName])return this.activate(a,unary[opName]);
|
| 1909 |
+
if(opName==='Sum')return this.reduce(a,false);if(opName==='Mean')return this.reduce(a,true);if(opName==='Transpose')return this.transpose(a);if(opName==='Softmax')return this.softmax(a,false);if(opName==='LogSoftmax')return this.softmax(a,true);if(opName==='LayerNorm')return this.norm(a,false,ctx.epsilon??1e-5);if(opName==='RMSNorm')return this.norm(a,true,ctx.epsilon??1e-6);
|
| 1910 |
+
if(opName==='Reshape'){const shape=inputs[1];return new Tensor(a.numericData(),Array.from(shape),false,'cpu_reshape',a.runner,{device:'cpu',dtype:a.dtype});}
|
| 1911 |
+
return null;
|
| 1912 |
+
}
|
| 1913 |
+
};
|
| 1914 |
+
|
| 1915 |
// --- Tensor Class ---
|
| 1916 |
let tensorIdCounter=0; let textureIdCounter=0; class Tensor{
|
| 1917 |
constructor(data,shape,requires_grad=false,name=null,webglRunner=null,options={}){
|
|
|
|
| 1928 |
this.size=shapeInfo.size;
|
| 1929 |
this.name=name||`t${this.id}`;
|
| 1930 |
this._destroyed=false;
|
| 1931 |
+
this.__gmlProperties=new Set(['shape','size','requires_grad','grad','name','dtype','device']);
|
| 1932 |
+
this.__gmlMethods=new Set(['backward','relu','relu6','leaky_relu','elu','selu','gelu','silu','swish','softplus','mish','sigmoid','tanh','softmax','log_softmax','layer_norm','rms_norm','l2_norm','rope','causal_mask','window_mask','shift','causal_conv1d','affine_scan','repeat_interleave','patchify','frame','slice','concat','dropout','sum','mean','square','abs','exp','log','sqrt','transpose','reshape','permute','batched_matmul','zero_grad','to','cpu','gpu','f32','f16','i8']);
|
| 1933 |
+
this.dtype=GMLDType.normalize(options.dtype || (data instanceof Int8Array?'i8':(webglRunner?._allocationDtype||'f32')));
|
| 1934 |
+
this.quantization=options.quantization?{...options.quantization}:null;
|
| 1935 |
+
this.storageDtype=this.dtype;
|
| 1936 |
this.data=null;
|
| 1937 |
this.texture=null;
|
| 1938 |
this.texWidth=0;
|
| 1939 |
this.texHeight=0;
|
| 1940 |
this.rowAligned=false;
|
| 1941 |
+
this.requires_grad=this.dtype==='i8'?false:requires_grad;
|
| 1942 |
this._grad=null;
|
| 1943 |
this._grad_fn=null;
|
| 1944 |
this._ctx=null;
|
|
|
|
| 1946 |
this._materializing=false;
|
| 1947 |
this._deferAllocation=Boolean(options?.deferAllocation);
|
| 1948 |
if(!this.runner&&requires_grad)console.warn(`Tensor ${this.name} requires_grad but no runner.`);
|
| 1949 |
+
if(data instanceof Float32Array||data instanceof Int8Array||Array.isArray(data)){
|
| 1950 |
if(data.length!==this.size)throw Error(`Data size ${data.length} mismatch with shape product ${this.size} for ${this.name}`);
|
| 1951 |
+
const sourceDtype=data instanceof Int8Array?'i8':'f32';
|
| 1952 |
+
const cast=GMLDType.cast(data,sourceDtype,this.dtype,options.quantization||null);this.data=cast.data;this.quantization=cast.quantization;
|
| 1953 |
+
}else if(data===null&&this.runner&&!this._deferAllocation&&options.device!=='cpu'){
|
|
|
|
|
|
|
| 1954 |
this._allocateGPUTexture();
|
| 1955 |
}else if(data!==null){
|
| 1956 |
+
throw Error(`Invalid data type for ${this.name}. Expected numeric Array, Float32Array, Int8Array, or null.`);
|
| 1957 |
}
|
| 1958 |
+
if(this.data&&this.runner&&options.device!=='cpu'){
|
| 1959 |
this.toGPU();
|
| 1960 |
+
}else if(!this.data&&!this.texture&&this.runner&&!this._deferAllocation&&options.device!=='cpu'){
|
| 1961 |
this._allocateGPUTexture();
|
| 1962 |
}
|
| 1963 |
}
|
|
|
|
| 2006 |
if(this.size===0){this.texWidth=0;this.texHeight=0;if(this.texture)this.runner.deleteTexture(this.texture);this.texture=null;return;}
|
| 2007 |
const{width,height,rowAligned=false}=this.runner._getTextureSizeForShape(this.shape);
|
| 2008 |
if(this.texture&&(this.texWidth!==width||this.texHeight!==height)){this.runner.deleteTexture(this.texture);this.texture=null;}
|
| 2009 |
+
if(!this.texture){this.texture=this.runner.createTexture(width,height,null,this.dtype==='i8'?'f32':this.dtype);}
|
| 2010 |
+
this.storageDtype=this.texture?.__meta?.dtype||(this.dtype==='i8'?'f32':this.dtype);
|
| 2011 |
this.texWidth=width;
|
| 2012 |
this.texHeight=height;
|
| 2013 |
this.rowAligned=Boolean(rowAligned);
|
|
|
|
| 2019 |
if(!this.data){if(!this.texture)this._allocateGPUTexture();return;}
|
| 2020 |
if(this.size===0)return;
|
| 2021 |
if(!this.texture)this._allocateGPUTexture();
|
| 2022 |
+
const uploadData=this.numericData();
|
| 2023 |
+
const p=this.runner._padDataForTexture(uploadData,this.texWidth,this.texHeight);
|
| 2024 |
const gl=this.runner.gl;
|
| 2025 |
gl.bindTexture(gl.TEXTURE_2D,this.texture);
|
| 2026 |
gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,this.texWidth,this.texHeight,gl.RGBA,gl.FLOAT,p);
|
|
|
|
| 2030 |
this._assertUsable('toCPU()');
|
| 2031 |
if(this._lazyPlan){this.ensureMaterialized();}
|
| 2032 |
if(!this.runner||!this.texture)return this.data;
|
| 2033 |
+
if(this.data instanceof Float32Array||this.data instanceof Int8Array)return this.data;
|
| 2034 |
+
if(this.texWidth===0||this.texHeight===0)return this.dtype==='i8'?new Int8Array(0):new Float32Array(0);
|
| 2035 |
+
const read=this.runner.readTexture(this);
|
| 2036 |
+
if(read===null){console.error(`Failed to read GPU data for ${this.name}.`);return null;}
|
| 2037 |
+
const cast=GMLDType.cast(read,'f32',this.dtype,this.quantization);this.data=cast.data;if(this.dtype!=='i8')this.quantization=cast.quantization;
|
| 2038 |
return this.data;
|
| 2039 |
}
|
| 2040 |
+
numericData(){
|
| 2041 |
+
this._assertUsable('numericData()');
|
| 2042 |
+
const raw=(this.data instanceof Float32Array||this.data instanceof Int8Array)?this.data:this.toCPU();
|
| 2043 |
+
if(raw==null)return null;
|
| 2044 |
+
return GMLDType.toFloat32(raw,this.dtype,this.quantization);
|
| 2045 |
+
}
|
| 2046 |
+
get device(){return (this.texture||this._lazyPlan)?'gpu':'cpu';}
|
| 2047 |
+
cpu(){
|
| 2048 |
+
this._assertUsable('cpu()');if(this._lazyPlan)this.ensureMaterialized();if(this.texture){this.toCPU();this.runner.deleteTexture(this.texture);this.texture=null;this.texWidth=0;this.texHeight=0;this.rowAligned=false;}return this;
|
| 2049 |
+
}
|
| 2050 |
+
gpu(){
|
| 2051 |
+
this._assertUsable('gpu()');if(!this.runner)throw new GMLValidationError(`Tensor '${this.name}' has no GPU runtime.`);this.toGPU();this.data=null;return this;
|
| 2052 |
+
}
|
| 2053 |
+
_castInPlace(dtype){
|
| 2054 |
+
dtype=GMLDType.normalize(dtype);if(dtype===this.dtype)return this;const wasGpu=Boolean(this.texture);const f=this.numericData();if(this.texture){this.runner.deleteTexture(this.texture);this.texture=null;this.texWidth=0;this.texHeight=0;}
|
| 2055 |
+
const cast=GMLDType.cast(f,'f32',dtype,null);this.dtype=dtype;this.data=cast.data;this.quantization=cast.quantization;if(dtype==='i8')this.requires_grad=false;if(wasGpu)this.gpu();return this;
|
| 2056 |
+
}
|
| 2057 |
+
f32(){return this._castInPlace('f32');}
|
| 2058 |
+
f16(){return this._castInPlace('f16');}
|
| 2059 |
+
i8(){return this._castInPlace('i8');}
|
| 2060 |
+
to(target){const t=String(target??'').toLowerCase();if(t==='cpu')return this.cpu();if(t==='gpu'||t==='webgl')return this.gpu();return this._castInPlace(t);}
|
| 2061 |
static viewOf(source,newShape,name=null){
|
| 2062 |
GMLValidate.tensor(source,'view source',{runner:source.runner,requireData:true});
|
| 2063 |
const info=GMLValidate.shape(newShape,'view shape');
|
| 2064 |
if(info.size!==source.size)throw new GMLValidationError(`View size mismatch: [${source.shape}] has ${source.size} values but [${newShape}] requires ${info.size}.`);
|
| 2065 |
if(source._lazyPlan)source.ensureMaterialized();
|
| 2066 |
+
const view=new Tensor(null,newShape,false,name||`${source.name}_view`,source.runner,{deferAllocation:true,dtype:source.dtype,quantization:source.quantization});
|
| 2067 |
if(source.texture&&source.runner){
|
| 2068 |
view.texture=source.runner.retainTexture(source.texture);
|
| 2069 |
view.texWidth=source.texWidth;view.texHeight=source.texHeight;
|
|
|
|
| 2084 |
else if (this._grad_fn) gradStatus = "Pending (Intermediate)";
|
| 2085 |
else gradStatus = "Yes (Leaf)";
|
| 2086 |
}
|
| 2087 |
+
return `Tensor(${this.name}, shape=[${this.shape.join(',')}], dtype=${this.dtype}, device=${this.device}, grad=${gradStatus}${t})`;
|
| 2088 |
}
|
| 2089 |
async backward(gradient=null){this._assertUsable('backward()');if(this._lazyPlan)this.ensureMaterialized();if(!this.requires_grad){console.warn(`Cannot call backward() on ${this.name} because requires_grad is false.`);return;}if(!this.runner)throw Error(`No WebGL runner available for backward pass of ${this.name}.`);const visited=new Set();const nodes=[];function build(n){if(!n||visited.has(n.id)||!(n instanceof Tensor))return;visited.add(n.id);if(n._ctx&&n._ctx.inputs)n._ctx.inputs.forEach(i=>build(i));nodes.push(n);}build(this);if(this._grad&&this._grad!==gradient&&gradient !== null){console.warn(`Tensor ${this.name} already has a gradient. It will be overwritten by the new gradient in backward().`);this._grad.destroy();this._grad=null;}if(!this._grad || gradient !== null){ if(gradient){if(!(gradient instanceof Tensor))throw Error("Gradient passed to backward() must be a Tensor.");if(gradient.shape.toString()!==this.shape.toString())throw Error(`Gradient shape mismatch for ${this.name}: expected ${this.shape}, got ${gradient.shape}.`);this._grad=gradient;if(!this._grad.texture && this._grad.size > 0)this._grad.toGPU();}else{if(this.size!==1)console.warn(`Implicit gradient of 1.0 created for non-scalar Tensor ${this.name} (shape [${this.shape}]) in backward().`);const ones=new Float32Array(this.size).fill(1.0);this._grad=new Tensor(ones,this.shape,false,`${this.name}_grad_implicit`,this.runner);if(this._grad.size > 0) this._grad.toGPU();}this._grad.requires_grad=false;} const yieldEveryNodes=4; for(let i=nodes.length-1;i>=0;i--){if(((nodes.length-1-i)%yieldEveryNodes)===0)await GMLUiScheduler.checkpoint();const node=nodes[i];if(node._grad_fn){if(node._grad){if(node._grad._lazyPlan)node._grad.ensureMaterialized();if(!node._grad.texture&&node._grad.size>0)node._grad.toGPU();const gradBeforeBackward=node._grad;const maybePromise=node._grad_fn(node._ctx);if(maybePromise&&typeof maybePromise.then==='function')await maybePromise;if(node!==this&&node._grad===gradBeforeBackward){gradBeforeBackward.destroy();node._grad=null;}}else if(node.requires_grad){console.warn(` Node ${node.name} requires grad but has no gradient computed during backward pass.`);}}else if(node.requires_grad&&!node._grad&&node!==this){console.warn(` Leaf Tensor ${node.name} requires grad but has no gradient after backward pass (and is not the initial Tensor).`);}}}
|
| 2090 |
add(other){if(!(other instanceof Tensor))throw Error("Addition requires both operands to be Tensors.");return Add.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
|
|
|
| 2093 |
div(other){return Div.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
| 2094 |
square(){ return Square.apply({runner:this.runner, inputs:[this]}, this); }
|
| 2095 |
transpose(){return Transpose.apply({runner:this.runner, inputs:[this]}, this);}
|
| 2096 |
+
matmul(other){if(!(other instanceof Tensor))throw Error("Matrix multiplication requires both operands to be Tensors.");if(this.device==='cpu'&&other.device==='cpu'&&!this.requires_grad&&!other.requires_grad)return GMLCpu.matmul(this,other);return MatMul.apply({runner:this.runner, inputs:[this, other]}, this,other);}
|
| 2097 |
batched_matmul(other,transposeA=false,transposeB=false){if(!(other instanceof Tensor))throw Error('batched_matmul requires a Tensor.');return BatchedMatMul.apply({runner:this.runner,inputs:[this,other],disableFusion:true},this,other,transposeA,transposeB);}
|
| 2098 |
permute(axes){return Permute.apply({runner:this.runner,inputs:[this],disableFusion:true},this,axes);}
|
| 2099 |
relu(){ return ReLU.apply({runner:this.runner, inputs:[this]}, this); }
|
|
|
|
| 2173 |
}
|
| 2174 |
}
|
| 2175 |
const output_req_grad=tensorsIn.some(t=>t instanceof Tensor && t.requires_grad);
|
| 2176 |
+
const cpuInputs=tensorsIn.filter(t=>t.device==='cpu');
|
| 2177 |
+
if(cpuInputs.length){
|
| 2178 |
+
if(cpuInputs.length!==tensorsIn.length)throw new GMLValidationError(`${this.name} received tensors on different devices.`,`Move them explicitly with .cpu() or .gpu(); GML never silently crosses devices.`);
|
| 2179 |
+
if(output_req_grad)throw new GMLValidationError(`CPU autograd for ${this.name} is not enabled in v0.3.`,`Use .gpu() for training; CPU execution is intended for inference, debugging, and speed comparisons.`);
|
| 2180 |
+
const cpuOutput=GMLCpu.apply(this.name,inputs,ctx);if(cpuOutput)return cpuOutput;
|
| 2181 |
+
throw new GMLValidationError(`${this.name} has no CPU kernel yet.`,`Call .gpu() before this operation. GML will not silently move it for you.`);
|
| 2182 |
+
}
|
| 2183 |
if (!output_req_grad && runner.fusionCompiler && !ctx.disableFusion) {
|
| 2184 |
const fusedOutput = runner.fusionCompiler.tryCreateTensor(this.name, inputs, ctx);
|
| 2185 |
if (fusedOutput) {
|
|
|
|
| 2187 |
}
|
| 2188 |
}
|
| 2189 |
for(const input of tensorsIn){if(input instanceof Tensor && !input.texture && input.size>0)input.toGPU();}
|
| 2190 |
+
const previousAllocationDtype=runner._allocationDtype;
|
| 2191 |
+
runner._allocationDtype=ctx.outputDtype||GMLDType.promote(tensorsIn);
|
| 2192 |
+
let output;
|
| 2193 |
+
try{output=this.forward(ctx,...inputs);}finally{runner._allocationDtype=previousAllocationDtype;}
|
| 2194 |
if(!(output instanceof Tensor)) {
|
| 2195 |
throw new Error(`${this.name}.forward must return a Tensor (returned type: ${typeof output})`);
|
| 2196 |
}
|
|
|
|
| 4189 |
}
|
| 4190 |
}
|
| 4191 |
|
| 4192 |
+
class GMLCharLanguageModel {
|
| 4193 |
+
constructor(text,parserRef,context=null){
|
| 4194 |
+
this.parser=parserRef;this.trainingText=String(text??'');const chars=GMLUnicode.graphemes(this.trainingText);if(chars.length<3)throw new GMLValidationError('A language-model training string needs at least 3 characters.');
|
| 4195 |
+
this.vocab=[];const seen=new Set();for(const ch of chars)if(!seen.has(ch)){seen.add(ch);this.vocab.push(ch);}this.context=context||this._inferContext(chars);this.transitions=new Map();this.unigram=new Map();
|
| 4196 |
+
for(let i=0;i<chars.length;i++)this.unigram.set(chars[i],(this.unigram.get(chars[i])||0)+1);
|
| 4197 |
+
for(let n=1;n<=this.context;n++)for(let i=n;i<chars.length;i++){const key=this._key(chars.slice(i-n,i));const next=chars[i];let counts=this.transitions.get(key);if(!counts){counts=new Map();this.transitions.set(key,counts);}counts.set(next,(counts.get(next)||0)+1);}
|
| 4198 |
+
this.task='language';this.seed=parserRef?.seedValue??1337;this.__gmlObjectType='char_lm';this.__gmlProperties=new Set(['task','context','vocab','seed']);this.__gmlMethods=new Set(['generate','summary','save']);
|
| 4199 |
+
}
|
| 4200 |
+
_key(parts){return parts.join('');}
|
| 4201 |
+
_inferContext(chars){for(let n=1;n<=Math.min(8,chars.length-1);n++){const nextBy=new Map();let ambiguous=false;for(let i=n;i<chars.length;i++){const k=this._key(chars.slice(i-n,i)),v=chars[i];if(nextBy.has(k)&&nextBy.get(k)!==v){ambiguous=true;break;}nextBy.set(k,v);}if(!ambiguous)return n;}return Math.min(8,chars.length-1);}
|
| 4202 |
+
_best(counts){let best=null,bestN=-1;for(const [ch,n] of counts||[]){if(n>bestN){best=ch;bestN=n;}}return best;}
|
| 4203 |
+
generate(prompt,count=32,temperature=0){GMLValidate.integer(count,'generate length',{min:0,max:1000000});let chars=GMLUnicode.graphemes(String(prompt??''));for(let step=0;step<count;step++){let counts=null;for(let n=Math.min(this.context,chars.length);n>=1&&!counts;n--)counts=this.transitions.get(this._key(chars.slice(chars.length-n)));if(!counts)counts=this.unigram;let next;if(Number(temperature)>0&&counts.size>1){const temp=Math.max(1e-4,Number(temperature)),items=[...counts.entries()],weights=items.map(([,n])=>Math.pow(n,1/temp)),total=weights.reduce((a,b)=>a+b,0),r=(this.parser?.nextRandom?.()??Math.random())*total;let acc=0;for(let i=0;i<items.length;i++){acc+=weights[i];if(r<=acc){next=items[i][0];break;}}}else next=this._best(counts);if(next==null)break;chars.push(next);}return chars.join('');}
|
| 4204 |
+
summary(){return `CharLM(context=${this.context}, vocab=${this.vocab.length}, training_chars=${GMLUnicode.length(this.trainingText)}, seed=${this.seed})`;}
|
| 4205 |
+
toString(){return `CharLanguageModel {
|
| 4206 |
+
context: ${this.context},
|
| 4207 |
+
vocab: ${JSON.stringify(this.vocab)},
|
| 4208 |
+
trainingChars: ${GMLUnicode.length(this.trainingText)},
|
| 4209 |
+
seed: ${this.seed}
|
| 4210 |
+
}`;}
|
| 4211 |
+
toBundleMeta(){return {kind:'char_lm',task:this.task,context:this.context,vocab:this.vocab,seed:this.seed,trainingText:this.trainingText,transitions:[...this.transitions].map(([k,v])=>[k,[...v]])};}
|
| 4212 |
+
save(filename='char-lm.gmlm'){return GMLModelBundle.download(this,filename,typeof getEditorSource==='function'?getEditorSource():'');}
|
| 4213 |
+
}
|
| 4214 |
+
|
| 4215 |
+
const GMLModelBundle = {
|
| 4216 |
+
magic:'GMLMOD1\n',
|
| 4217 |
+
_tensorRecord(tensor,name,offset){
|
| 4218 |
+
const floats=tensor.numericData();let bytes,dtype=tensor.dtype,quantization=tensor.quantization?{...tensor.quantization}:null;
|
| 4219 |
+
if(dtype==='i8'){const q=tensor.data instanceof Int8Array?tensor.data:GMLDType.quantizeI8(floats).data;bytes=new Uint8Array(q.buffer,q.byteOffset,q.byteLength);}
|
| 4220 |
+
else if(dtype==='f16'){const half=new Uint16Array(floats.length);for(let i=0;i<floats.length;i++)half[i]=GMLDType.floatToHalfBits(floats[i]);bytes=new Uint8Array(half.buffer);}
|
| 4221 |
+
else bytes=new Uint8Array(floats.buffer,floats.byteOffset,floats.byteLength);
|
| 4222 |
+
return {meta:{name,dtype,shape:tensor.shape.slice(),offset,length:bytes.byteLength,quantization},bytes:new Uint8Array(bytes)};
|
| 4223 |
+
},
|
| 4224 |
+
encode(model,source=''){
|
| 4225 |
+
let params=[],modelMeta;
|
| 4226 |
+
if(model instanceof GMLModel){params=model.layers.flatMap((l,i)=>[[`layer${i+1}.weight`,l.W],[`layer${i+1}.bias`,l.B]]);modelMeta={kind:'mlp',layerSizes:model.layerSizes,activation:model.activation,task:model.task,seed:model.seed,inputEncoding:model.inputEncoding,preprocessor:model.preprocessor?.enabled?{enabled:true,mean:Array.from(model.preprocessor.mean.numericData()),scale:Array.from(model.preprocessor.scale.numericData())}:{enabled:false},targetPreprocessor:model.targetPreprocessor||null};}
|
| 4227 |
+
else if(model instanceof GMLCustomArchitectureModel){params=[...model.scope.params.entries()];modelMeta={kind:'custom',architecture:model.scope.name,functionName:model.functionName,task:model.task};}
|
| 4228 |
+
else if(model instanceof GMLCharLanguageModel){modelMeta=model.toBundleMeta();}
|
| 4229 |
+
else throw new GMLValidationError('save() expects a model created by learn(), mlp(), or architecture().model().');
|
| 4230 |
+
let offset=0;const records=[];for(const [name,t] of params){const rec=this._tensorRecord(t,name,offset);records.push(rec);offset+=rec.bytes.byteLength;}
|
| 4231 |
+
const header={format:'gml-model',version:1,runtime:'gml-v0.3',createdAt:new Date().toISOString(),source:String(source||''),model:modelMeta,tensors:records.map(r=>r.meta)};
|
| 4232 |
+
const headerBytes=new TextEncoder().encode(JSON.stringify(header)),magicBytes=new TextEncoder().encode(this.magic),prefix=magicBytes.length+8,total=prefix+headerBytes.length+offset,buffer=new ArrayBuffer(total),view=new Uint8Array(buffer);view.set(magicBytes,0);new DataView(buffer).setBigUint64(magicBytes.length,BigInt(headerBytes.length),true);view.set(headerBytes,prefix);let cursor=prefix+headerBytes.length;for(const r of records){view.set(r.bytes,cursor);cursor+=r.bytes.length;}return buffer;
|
| 4233 |
+
},
|
| 4234 |
+
decode(buffer){const bytes=new Uint8Array(buffer),magicBytes=new TextEncoder().encode(this.magic);for(let i=0;i<magicBytes.length;i++)if(bytes[i]!==magicBytes[i])throw new Error('Not a GML model bundle.');const n=Number(new DataView(buffer).getBigUint64(magicBytes.length,true)),start=magicBytes.length+8,header=JSON.parse(new TextDecoder().decode(bytes.subarray(start,start+n))),dataStart=start+n;return {header,tensorBytes(name){const t=header.tensors.find(x=>x.name===name);if(!t)throw new Error(`Tensor '${name}' not found.`);return bytes.slice(dataStart+t.offset,dataStart+t.offset+t.length);}};},
|
| 4235 |
+
_decodeTensor(record,raw,runner){let values;if(record.dtype==='i8'){values=new Int8Array(raw.buffer,raw.byteOffset,raw.byteLength);return new Tensor(new Int8Array(values),record.shape,true,record.name,runner,{dtype:'i8',quantization:record.quantization||{scale:1,zeroPoint:0}});}if(record.dtype==='f16'){const count=raw.byteLength/2,u=new Uint16Array(raw.buffer,raw.byteOffset,count),f=new Float32Array(count);for(let i=0;i<count;i++)f[i]=GMLDType.halfBitsToFloat(u[i]);return new Tensor(f,record.shape,true,record.name,runner,{dtype:'f16'});}values=new Float32Array(raw.buffer.slice(raw.byteOffset,raw.byteOffset+raw.byteLength));return new Tensor(values,record.shape,true,record.name,runner,{dtype:'f32'});},
|
| 4236 |
+
restore(buffer,runner,parserRef){const decoded=this.decode(buffer),meta=decoded.header.model;if(meta.kind==='char_lm')return new GMLCharLanguageModel(meta.trainingText,parserRef,meta.context);if(meta.kind==='custom'){if(!parserRef)throw new GMLValidationError('Restoring a custom architecture needs a GML parser so its bundled functions can be registered.');const processed=parserRef.preprocessCode(String(decoded.header.source||'').split('\n'));for(const item of processed)if(item.type==='function')parserRef.defineFunction(item.name,item.args,item.body);const scope=new GMLArchitectureScope(meta.architecture,parserRef,runner);for(const rec of decoded.header.tensors){const t=this._decodeTensor(rec,decoded.tensorBytes(rec.name),runner);scope.params.set(rec.name,t);parserRef.trackTensor(t);}return new GMLCustomArchitectureModel(scope,meta.functionName);}if(meta.kind!=='mlp')return {source:decoded.header.source,metadata:meta,tensors:decoded.header.tensors.map(r=>({record:r,bytes:decoded.tensorBytes(r.name)}))};const model=new GMLModel(meta.layerSizes,meta.activation,runner,parserRef,meta.seed??1337);model.task=meta.task||'regression';model.inputEncoding=meta.inputEncoding||null;const byName=new Map(decoded.header.tensors.map(r=>[r.name,r]));for(let i=0;i<model.layers.length;i++){for(const [key,slot] of [[`layer${i+1}.weight`,'W'],[`layer${i+1}.bias`,'B']]){const rec=byName.get(key);if(!rec)throw new Error(`Missing ${key} in model bundle.`);model.layers[i][slot].destroy();model.layers[i][slot]=this._decodeTensor(rec,decoded.tensorBytes(key),runner);}}if(meta.preprocessor?.enabled){model.preprocessor={enabled:true,mean:new Tensor(meta.preprocessor.mean,[1,meta.preprocessor.mean.length],false,'restore_mean',runner),scale:new Tensor(meta.preprocessor.scale,[1,meta.preprocessor.scale.length],false,'restore_scale',runner)};}model.targetPreprocessor=meta.targetPreprocessor||null;return model;},
|
| 4237 |
+
download(model,filename='model.gmlm',source=''){const name=String(filename||'model.gmlm').endsWith('.gmlm')?String(filename):`${filename}.gmlm`,buffer=this.encode(model,source),url=URL.createObjectURL(new Blob([buffer],{type:'application/octet-stream'})),a=document.createElement('a');a.href=url;a.download=name;a.style.display='none';document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);return `Saved ${name} (${formatBytes(buffer.byteLength)})`;}
|
| 4238 |
+
};
|
| 4239 |
+
if(typeof window!=='undefined')window.GMLModelBundle=GMLModelBundle;
|
| 4240 |
+
|
| 4241 |
class GMLModel {
|
| 4242 |
constructor(layerSizes, activation, runner, parserRef, seed = 1337) {
|
| 4243 |
GMLValidate.shape(layerSizes, 'mlp layer sizes', { maxRank: 64 });
|
|
|
|
| 4267 |
this._adamState = new Map();
|
| 4268 |
this._adamStep = 0;
|
| 4269 |
this._scheduler = new GMLCooperativeScheduler(8);
|
| 4270 |
+
this.executionDevice='gpu';
|
| 4271 |
+
this.__gmlProperties = new Set(['inputSize', 'outputSize', 'activation', 'task', 'history', 'plan', 'inputEncoding','dtype','device']);
|
| 4272 |
+
this.__gmlMethods = new Set(['fit', 'predict', 'evaluate', 'summary', 'parameters', 'explain','to','cpu','gpu','f32','f16','i8','save']);
|
| 4273 |
|
| 4274 |
let state = (seed >>> 0) || 1;
|
| 4275 |
const random = () => {
|
|
|
|
| 4300 |
}
|
| 4301 |
|
| 4302 |
parameters() { return this.layers.flatMap(layer => [layer.W, layer.B]); }
|
| 4303 |
+
_stateTensors(){const out=this.parameters().slice();if(this.preprocessor?.enabled){if(this.preprocessor.mean instanceof Tensor)out.push(this.preprocessor.mean);if(this.preprocessor.scale instanceof Tensor)out.push(this.preprocessor.scale);}return out;}
|
| 4304 |
+
get dtype(){return this.parameters()[0]?.dtype||'f32';}
|
| 4305 |
+
get device(){return this.executionDevice;}
|
| 4306 |
+
cpu(){this.executionDevice='cpu';this._stateTensors().forEach(p=>p.cpu());return this;}
|
| 4307 |
+
gpu(){this.executionDevice='gpu';this._stateTensors().forEach(p=>p.gpu());return this;}
|
| 4308 |
+
f32(){this._stateTensors().forEach(p=>p.f32());return this;}
|
| 4309 |
+
f16(){this._stateTensors().forEach(p=>p.f16());return this;}
|
| 4310 |
+
i8(){this.parameters().forEach(p=>p.i8());return this;}
|
| 4311 |
+
to(target){const t=String(target??'').toLowerCase();if(t==='cpu')return this.cpu();if(t==='gpu'||t==='webgl')return this.gpu();if(t==='f32')return this.f32();if(t==='f16')return this.f16();if(t==='i8'||t==='int8')return this.i8();throw new GMLValidationError(`Unknown model target '${target}'.`,`Use cpu, gpu, f32, f16, or i8.`);}
|
| 4312 |
+
save(filename='model.gmlm'){return GMLModelBundle.download(this,filename,typeof getEditorSource==='function'?getEditorSource():'');}
|
| 4313 |
|
| 4314 |
summary() {
|
| 4315 |
const params = this.parameters().reduce((sum, p) => sum + p.size, 0);
|
|
|
|
| 4331 |
` tensors: [`
|
| 4332 |
];
|
| 4333 |
this.layers.forEach((layer,index)=>{
|
| 4334 |
+
lines.push(` { weight: Tensor(shape=[${layer.W.shape.join(', ')}], dtype=${layer.W.dtype}, device=${layer.W.device}), bias: Tensor(shape=[${layer.B.shape.join(', ')}], dtype=${layer.B.dtype}, device=${layer.B.device}) }${index===this.layers.length-1?'':','}`);
|
| 4335 |
});
|
| 4336 |
lines.push(` ]${this.plan?',':''}`);
|
| 4337 |
if(this.plan)lines.push(` plan: ${JSON.stringify(this.plan.summary())}`);
|
|
|
|
| 4420 |
GMLValidate.tensor(input, 'model input', { runner: this.runner, requireData: true });
|
| 4421 |
if (input.shape.length !== 2) throw new GMLValidationError(`Model input must be 2D [batch, features], got [${input.shape}].`, `Expected feature width ${this.inputSize}.`);
|
| 4422 |
if (input.shape[1] !== this.inputSize) throw new GMLValidationError(`Model expects ${this.inputSize} input features, got ${input.shape[1]}.`);
|
| 4423 |
+
if(this.executionDevice==='cpu'){
|
| 4424 |
+
let value=input;if(value.device!=='cpu')value.cpu();
|
| 4425 |
+
if(this.preprocessor?.enabled){const src=value.numericData(),mean=this.preprocessor.mean.numericData(),scale=this.preprocessor.scale.numericData(),out=new Float32Array(src.length),cols=value.shape[1];for(let i=0;i<src.length;i++)out[i]=(src[i]-mean[i%cols])/scale[i%cols];value=new Tensor(out,value.shape.slice(),false,'cpu_preprocess',this.runner,{device:'cpu'});}
|
| 4426 |
+
for(let i=0;i<this.layers.length;i++){const layer=this.layers[i];value=GMLCpu.matmul(value,layer.W,layer.B);if(i<this.layers.length-1)value=GMLCpu.activate(value,this.activation);}
|
| 4427 |
+
return value;
|
| 4428 |
+
}
|
| 4429 |
let value = this._preprocess(input);
|
| 4430 |
for (let i = 0; i < this.layers.length; i++) {
|
| 4431 |
const layer = this.layers[i];
|
|
|
|
| 4816 |
}
|
| 4817 |
|
| 4818 |
async fit(xValue, yValue, epochs = 100, learningRate = 0.01, batchSize = 64) {
|
| 4819 |
+
if(this.executionDevice==='cpu')throw new GMLValidationError('This model is currently on CPU. CPU inference is supported; training remains GPU-optimized in v0.3.','Call Model.gpu() before fit(), then Model.cpu() afterward if you want CPU inference.');
|
| 4820 |
GMLValidate.epochs(epochs); GMLValidate.learningRate(learningRate); GMLValidate.batchSize(batchSize);
|
| 4821 |
const X = this.parser.coerceValueToTensor(xValue, 'fit X');
|
| 4822 |
const Y = this.parser.coerceValueToTensor(yValue, 'fit Y');
|
|
|
|
| 4864 |
const tp = this.targetPreprocessor?.enabled ? this.targetPreprocessor : { mean: 0, scale: 1 };
|
| 4865 |
for (let i = 0; i < z.length; i++) outputData[i] = z[i] * tp.scale + tp.mean;
|
| 4866 |
}
|
| 4867 |
+
const result = new Tensor(outputData, logits.shape.slice(), false, 'prediction', this.runner,{device:this.executionDevice==='cpu'?'cpu':'gpu',dtype:'f32'});
|
| 4868 |
this._cleanupGraph(graph, new Set([X, ...params]));
|
| 4869 |
return this.parser.trackTensor(result);
|
| 4870 |
} finally { params.forEach((p, i) => p.requires_grad = oldRequiresGrad[i]); }
|
|
|
|
| 5064 |
if(!scope.parser.functions.has(functionName))throw new GMLValidationError(`Function '${functionName}' is not defined.`);
|
| 5065 |
this.scope=scope;this.functionName=functionName;this.task='custom';
|
| 5066 |
this.__gmlProperties=new Set(['task']);
|
| 5067 |
+
this.__gmlMethods=new Set(['predict','parameters','zero_grad','step','summary','explain','to','cpu','gpu','f32','f16','i8','save']);
|
| 5068 |
}
|
| 5069 |
async predict(x){
|
| 5070 |
const input=this.scope.parser.coerceValueToTensor(x,'custom model input');
|
| 5071 |
return await this.scope.run(this.functionName,input);
|
| 5072 |
}
|
| 5073 |
parameters(){return this.scope.parameters();}
|
| 5074 |
+
cpu(){this.parameters().forEach(p=>p.cpu());return this;}
|
| 5075 |
+
gpu(){this.parameters().forEach(p=>p.gpu());return this;}
|
| 5076 |
+
f32(){this.parameters().forEach(p=>p.f32());return this;}
|
| 5077 |
+
f16(){this.parameters().forEach(p=>p.f16());return this;}
|
| 5078 |
+
i8(){this.parameters().forEach(p=>p.i8());return this;}
|
| 5079 |
+
to(target){const t=String(target??'').toLowerCase();if(t==='cpu')return this.cpu();if(t==='gpu'||t==='webgl')return this.gpu();if(t==='f32')return this.f32();if(t==='f16')return this.f16();if(t==='i8'||t==='int8')return this.i8();throw new GMLValidationError(`Unknown model target '${target}'.`);}
|
| 5080 |
+
save(filename='model.gmlm'){return GMLModelBundle.download(this,filename,typeof getEditorSource==='function'?getEditorSource():'');}
|
| 5081 |
zero_grad(){this.scope.zero_grad();return this;}
|
| 5082 |
async step(lr=0.001){await this.scope.step(lr);return this;}
|
| 5083 |
summary(){return `CustomModel(${this.scope.name}:${this.functionName}, parameters=${this.parameters().length})`;}
|
|
|
|
| 5496 |
const media=value._media?`, media=${JSON.stringify(value._media)}`:(value._mediaTokens?`, tokens=${JSON.stringify(value._mediaTokens)}`:'');
|
| 5497 |
return `Tensor { shape: [${value.shape.join(', ')}], size: ${value.size}, device: ${device}, requires_grad: ${Boolean(value.requires_grad)}${media}${sample} }`;
|
| 5498 |
}
|
| 5499 |
+
if(value instanceof GMLModel || value instanceof GMLCustomArchitectureModel || value instanceof GMLCharLanguageModel || value instanceof GMLArchitectureScope || value instanceof GMLMetrics || value instanceof GMLTaskPlan || value instanceof PlotSeries || value instanceof PlotHistory || value instanceof VirtualFileHandle) return value.toString();
|
| 5500 |
if(value===null)return 'null';if(value===undefined)return 'undefined';
|
| 5501 |
if(typeof value==='string')return JSON.stringify(value);if(typeof value==='number')return formatGmlNumber(value);if(typeof value==='boolean'||typeof value==='bigint')return String(value);if(typeof value==='function')return `[Function ${value.name||'anonymous'}]`;
|
| 5502 |
if(Array.isArray(value)){
|
|
|
|
| 6185 |
type: ['Type inspection', 'type(value)', 'Returns a short type name such as int, float, string, array, tensor, or model.'],
|
| 6186 |
tensor: ['Tensor', 'tensor(data)', 'Creates a tensor from numeric data.'],
|
| 6187 |
param: ['Trainable tensor', 'param(data)', 'Creates a trainable tensor.'],
|
| 6188 |
+
learn: ['Learning', 'model = learn(data) · model = learn(X, Y)', 'Infers a compact model from a text sequence or from supervised examples and targets.'],
|
| 6189 |
plan: ['Learning plan', 'plan(X, Y)', 'Shows the plan GML would use for a learning task.'],
|
| 6190 |
classify: ['Classification', 'classify(model, X, labels?)', 'Returns predicted class IDs or label names.'],
|
| 6191 |
predict: ['Prediction', 'predict(model, X)', 'Runs a model and returns its outputs.'],
|
|
|
|
| 6200 |
fit: ['Training', 'fit(model, X, Y, ...)', 'Trains an explicitly created model.'],
|
| 6201 |
matmul: ['Matrix multiplication', 'matmul(A, B)', 'Multiplies two rank-2 tensors.'],
|
| 6202 |
linear: ['Linear layer', 'linear(X, outputSize) · linear(X, W, B)', 'Projects the last feature dimension; architecture contexts can own and reuse parameters automatically.'],
|
| 6203 |
+
generate: ['Generate text', 'generate(Model, prompt, length?) · Model.generate(prompt, length?)', 'Continues a character language model from a prompt.'],
|
| 6204 |
+
save: ['Save model', 'save(Model, "model.gmlm") · Model.save("model.gmlm")', 'Downloads one self-contained bundle containing source, model metadata, and learned state or weights.'],
|
| 6205 |
+
to: ['Move or cast', 'Tensor.to("gpu") · Tensor.to("cpu") · Tensor.to("f16")', 'Moves a tensor between CPU/GPU execution or changes its dtype.'],
|
| 6206 |
+
cpu: ['CPU execution', 'Tensor.cpu() · Model.cpu()', 'Moves state to CPU execution for supported core operations and inference.'],
|
| 6207 |
+
gpu: ['GPU execution', 'Tensor.gpu() · Model.gpu()', 'Moves state to GPU execution.'],
|
| 6208 |
+
f16: ['Float16', 'Tensor.f16() · Model.f16()', 'Uses 16-bit floating-point storage on supported WebGL2 devices.'],
|
| 6209 |
+
f32: ['Float32', 'Tensor.f32() · Model.f32()', 'Uses 32-bit floating-point values.'],
|
| 6210 |
+
i8: ['Int8', 'Tensor.i8() · Model.i8()', 'Quantizes values to signed 8-bit storage; WebGL math promotes when native int8 compute is unavailable.'],
|
| 6211 |
sum: ['Sum', 'sum(x, axis?)', 'Adds tensor values, optionally along an axis.'],
|
| 6212 |
mean: ['Mean', 'mean(x, axis?)', 'Averages tensor values, optionally along an axis.'],
|
| 6213 |
relu: ['ReLU', 'relu(x)', 'Applies max(0, x).'],
|
|
|
|
| 6763 |
|
| 6764 |
defineFunction(name, argNames, processedBody) {
|
| 6765 |
if (this.functions.has(name)) console.warn(`Redefining func ${name}`);
|
| 6766 |
+
if(this.devMode&&this.getBuiltinNames().includes(name))this.log(`User function '${name}' shadows a builtin for this script.`);
|
| 6767 |
this.functions.set(name, { args: argNames, body: processedBody });
|
| 6768 |
this.log(` Defined function ${name}(${argNames.join(', ')})`);
|
| 6769 |
}
|
|
|
|
| 7297 |
'int','float','string','bool','type','image','audio','video','patchify','frame',
|
| 7298 |
'tensor','param','zeros','ones','add','sub','mul','div','square','abs','negate','transpose','permute','matmul','batched_matmul','linear',
|
| 7299 |
'relu','relu6','leaky_relu','elu','selu','gelu','silu','swish','swiglu','geglu','softplus','mish','sigmoid','tanh','softmax','log_softmax','layer_norm','rms_norm','l2_norm','rope','causal_mask','window_mask','attention','shift','causal_conv1d','affine_scan','repeat_interleave','concat','slice','one_hot','embedding','lerp','dropout','exp','log','sqrt','sum','mean','normalize','flatten','reshape',
|
| 7300 |
+
'mlp','architecture','plan','learn','fit','predict','evaluate','classify','generate','save','mse','binary_cross_entropy','bce','cross_entropy',
|
| 7301 |
'assert','assert_shape','assert_finite','describe','series','history','plot','dots','progress',
|
| 7302 |
'watch','write','append','read','files','remove','emit','clear_live','grid','table','md','markdown','graphemes','text_length',
|
| 7303 |
'print','print_grad','has_grad','step','zero_grad'
|
|
|
|
| 7393 |
if (evArgs.length !== 1) throw new GMLValidationError('type(value) expects exactly one value.');
|
| 7394 |
const value = evArgs[0];
|
| 7395 |
if (value instanceof Tensor) return 'tensor';
|
| 7396 |
+
if (value instanceof GMLModel || value instanceof GMLCustomArchitectureModel || value instanceof GMLCharLanguageModel) return 'model';
|
| 7397 |
if (value instanceof GMLArchitectureScope) return 'architecture';
|
| 7398 |
if (value instanceof GMLMetrics) return 'metrics';
|
| 7399 |
if (value instanceof GMLTaskPlan || value?.kind === 'semantic_ml_plan') return 'plan';
|
|
|
|
| 7485 |
if (evArgs.length !== 2) throw Error("matmul(a, b)");
|
| 7486 |
const a = tensorArg(evArgs[0], 'matmul arg 1');
|
| 7487 |
const b = tensorArg(evArgs[1], 'matmul arg 2');
|
| 7488 |
+
return this.trackTensor(a.matmul(b));
|
| 7489 |
}
|
| 7490 |
case 'linear': {
|
| 7491 |
if(evArgs.length===2 && typeof evArgs[1]==='number'){
|
|
|
|
| 7498 |
const a=tensorArg(evArgs[0],'linear input');
|
| 7499 |
const w=tensorArg(evArgs[1],'linear weight');
|
| 7500 |
const bias=tensorArg(evArgs[2],'linear bias');
|
| 7501 |
+
if(a.shape.length===2){if(a.device==='cpu'&&w.device==='cpu'&&bias.device==='cpu'&&!a.requires_grad&&!w.requires_grad&&!bias.requires_grad)return this.trackTensor(GMLCpu.matmul(a,w,bias));return this.trackTensor(Linear.apply({runner:this.runner,inputs:[a,w,bias],disableFusion:true,architecture:GMLArchitectureRuntime.current()?.family||null},a,w,bias));}
|
| 7502 |
if(a.shape.length===3){const inFeatures=a.shape[2];if(w.shape.length!==2||w.shape[0]!==inFeatures)throw new GMLValidationError(`linear weight must be [${inFeatures}, out_features] for input [${a.shape}].`);const rows=a.size/inFeatures,flat=this.trackTensor(Reshape.apply({runner:this.runner,inputs:[a]},a,[rows,inFeatures])),projected=this.trackTensor(Linear.apply({runner:this.runner,inputs:[flat,w,bias],disableFusion:true,architecture:GMLArchitectureRuntime.current()?.family||null},flat,w,bias));return this.trackTensor(Reshape.apply({runner:this.runner,inputs:[projected]},projected,[a.shape[0],a.shape[1],w.shape[1]]));}
|
| 7503 |
throw new GMLValidationError(`linear() expects rank-2 or rank-3 input, got [${a.shape}].`);
|
| 7504 |
}
|
|
|
|
| 7624 |
return GMLTaskPlanner.create(X, Y, this.runner, evArgs[2] ?? 'balanced');
|
| 7625 |
}
|
| 7626 |
case 'learn': {
|
| 7627 |
+
if(evArgs.length===1&&typeof evArgs[0]==='string')return new GMLCharLanguageModel(evArgs[0],this);
|
| 7628 |
if (evArgs.length < 2 || evArgs.length > 5) {
|
| 7629 |
+
throw new GMLValidationError("learn(data) for a text sequence, or learn(X, Y, intent?)", "GML infers a compact character language model from one string; supervised data uses learn(X, Y).");
|
| 7630 |
}
|
| 7631 |
const rawX = evArgs[0];
|
| 7632 |
const stringEncoded = isGmlStringDataset(rawX);
|
|
|
|
| 7664 |
await model._fitWithPlan(X, Y, semanticPlan);
|
| 7665 |
return model;
|
| 7666 |
}
|
| 7667 |
+
case 'generate': {
|
| 7668 |
+
if(evArgs.length<2||evArgs.length>4)throw new GMLValidationError('generate(model, prompt, length?, temperature?)');
|
| 7669 |
+
const [model,prompt,length=32,temperature=0]=evArgs;if(!(model instanceof GMLCharLanguageModel))throw new GMLValidationError('generate() currently expects a language model returned by learn(text).');return model.generate(prompt,length,temperature);
|
| 7670 |
+
}
|
| 7671 |
+
case 'save': {
|
| 7672 |
+
if(evArgs.length<1||evArgs.length>2)throw new GMLValidationError('save(model, filename?)');
|
| 7673 |
+
const filename=evArgs[1]??'model.gmlm';return GMLModelBundle.download(evArgs[0],filename,typeof getEditorSource==='function'?getEditorSource():'');
|
| 7674 |
+
}
|
| 7675 |
case 'mse': {
|
| 7676 |
if (evArgs.length !== 2) throw new GMLValidationError("mse(prediction, target)");
|
| 7677 |
const a = tensorArg(evArgs[0], 'mse prediction');
|
|
|
|
| 8047 |
}
|
| 8048 |
|
| 8049 |
// --- Syntax Highlighting Globals & Functions ---
|
| 8050 |
+
const GML_CORE_PRIMITIVES = Object.freeze({
|
| 8051 |
+
construct:['tensor','param'],
|
| 8052 |
+
elementwise:['add','sub','mul','div','exp','log','sqrt'],
|
| 8053 |
+
contract:['matmul'],
|
| 8054 |
+
reduce:['sum','mean'],
|
| 8055 |
+
shape:['reshape','permute','slice','concat'],
|
| 8056 |
+
window:['causal_conv1d','frame','patchify'],
|
| 8057 |
+
scan:['affine_scan'],
|
| 8058 |
+
convert:['to','f32','f16','i8','cpu','gpu'],
|
| 8059 |
+
compose:['function','architecture']
|
| 8060 |
+
});
|
| 8061 |
+
if(typeof window!=='undefined')window.GML_CORE_PRIMITIVES=GML_CORE_PRIMITIVES;
|
| 8062 |
+
|
| 8063 |
const _syntaxHighlightingElements = {
|
| 8064 |
codeInput: null,
|
| 8065 |
highlightingArea: null,
|
|
|
|
| 8083 |
runBenchmarkButton: null
|
| 8084 |
};
|
| 8085 |
const _appState = {
|
| 8086 |
+
selectedPreset: 'showcase',
|
| 8087 |
isBusy: false
|
| 8088 |
};
|
| 8089 |
const GML_WORKSPACE_KEY = 'gml.v0.3.workspace';
|
|
|
|
| 8124 |
function restoreWorkspace() {
|
| 8125 |
const storage=getWorkspaceStorage();if(!storage)return false;
|
| 8126 |
let state;
|
| 8127 |
+
try{
|
| 8128 |
+
const raw=storage.getItem(GML_WORKSPACE_KEY);
|
| 8129 |
+
if(!raw||!String(raw).trim())return false;
|
| 8130 |
+
state=JSON.parse(raw);
|
| 8131 |
+
}catch(_){
|
| 8132 |
+
try{storage.removeItem(GML_WORKSPACE_KEY);}catch(__){}
|
| 8133 |
+
return false;
|
| 8134 |
+
}
|
| 8135 |
+
// A blank saved editor is equivalent to no workspace: boot into the showcase.
|
| 8136 |
+
if(!state||typeof state.source!=='string'||!state.source.trim()){
|
| 8137 |
+
try{storage.removeItem(GML_WORKSPACE_KEY);}catch(_){}
|
| 8138 |
+
return false;
|
| 8139 |
+
}
|
| 8140 |
const input=_syntaxHighlightingElements.codeInput;if(!input)return false;
|
| 8141 |
_workspaceRestoring=true;
|
| 8142 |
try{
|
|
|
|
| 8293 |
},
|
| 8294 |
showcase: {
|
| 8295 |
label: 'Showcase',
|
| 8296 |
+
description: 'Train a tiny character language model, generate text, and optionally export it.',
|
| 8297 |
+
code: `# A complete first GML program: train from one sequence.
|
| 8298 |
+
Text = "Hello world from gml!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8299 |
|
| 8300 |
+
# GML infers a compact character model from the sequence.
|
| 8301 |
+
LanguageModel = learn(Text)
|
|
|
|
|
|
|
| 8302 |
|
| 8303 |
+
# User functions stay ordinary and reusable.
|
| 8304 |
+
function complete(model, prompt, characters) {
|
| 8305 |
+
return model.generate(prompt, characters)
|
| 8306 |
}
|
| 8307 |
|
| 8308 |
+
print LanguageModel
|
| 8309 |
+
print "Prompt: Hello "
|
| 8310 |
+
print "Prediction:", complete(LanguageModel, "Hello ", 15)
|
| 8311 |
+
|
| 8312 |
+
# Models can move/cast with the same small interface used by tensors:
|
| 8313 |
+
# SomeTensor = tensor([[1., 2.], [3., 4.]]).f16().gpu()
|
| 8314 |
+
# SomeTensor = SomeTensor.cpu()
|
| 8315 |
+
|
| 8316 |
+
# Uncomment to download one self-contained file containing source + model state:
|
| 8317 |
+
# LanguageModel.save("hello-world.gmlm")`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8318 |
},
|
| 8319 |
syntax: {
|
| 8320 |
label: 'Syntax Edge Cases',
|
|
|
|
| 9394 |
return 'binary_cross_entropy() and cross_entropy() execute forward/backward without GPU→CPU readback.';
|
| 9395 |
}
|
| 9396 |
},
|
| 9397 |
+
{
|
| 9398 |
+
name:'User Function Highlight Shadowing',
|
| 9399 |
+
async run(){const html=highlightSyntax('function linear(x) {\n return linear(x, 2)\n}\nsoftmax(x)');if(/syntax-builtin[^>]*>linear</.test(html))throw new Error('User-defined linear() still receives builtin coloring.');if(!/syntax-builtin[^>]*>softmax</.test(html))throw new Error('Normal builtin coloring was lost.');return 'User-defined function names visually override builtin coloring without changing unrelated builtins.';}
|
| 9400 |
+
},
|
| 9401 |
+
{
|
| 9402 |
+
name:'DType + Device Contract',
|
| 9403 |
+
async run(){const x=new Tensor(new Float32Array([0.1,-1.2,3.4,5.6]),[2,2],false,'dtype_probe',webglRunner,{device:'cpu'});if(x.dtype!=='f32'||x.device!=='cpu')throw new Error('Initial dtype/device mismatch.');x.f16();if(x.dtype!=='f16')throw new Error('f16 cast failed.');x.gpu();if(x.device!=='gpu')throw new Error('gpu move failed.');x.cpu().i8();if(x.dtype!=='i8'||x.device!=='cpu'||!x.quantization)throw new Error('int8 quantization contract failed.');x.f32();if(x.dtype!=='f32')throw new Error('f32 restoration failed.');x.destroy();return 'Tensor .f16/.f32/.i8 and .cpu/.gpu transitions preserve explicit dtype/device state.';}
|
| 9404 |
+
},
|
| 9405 |
+
{
|
| 9406 |
+
name:'Self-Contained Model Bundle',
|
| 9407 |
+
async run(){const m=new GMLModel([2,2],'relu',webglRunner,parser,1337);const buf=GMLModelBundle.encode(m,'X = 1');const decoded=GMLModelBundle.decode(buf);if(decoded.header.format!=='gml-model'||decoded.header.source!=='X = 1'||decoded.header.tensors.length!==2)throw new Error('Model bundle metadata is incomplete.');for(const t of decoded.header.tensors)if(decoded.tensorBytes(t.name).byteLength!==t.length)throw new Error(`Tensor payload mismatch for ${t.name}.`);const restored=GMLModelBundle.restore(buf,webglRunner,parser);if(!(restored instanceof GMLModel)||restored.layerSizes.join(',')!=='2,2')throw new Error('Model restore failed.');m.parameters().forEach(p=>p.destroy());restored.parameters().forEach(p=>p.destroy());return 'One binary bundle contains source, model metadata, dtype/shape records, all parameter bytes, and can reconstruct the model.';}
|
| 9408 |
+
},
|
| 9409 |
+
{
|
| 9410 |
+
name:'Custom Architecture Bundle Restore',
|
| 9411 |
+
async run(){const source='function portable_block(x) {\n return linear(x, 3)\n}';const items=parser.preprocessCode(source.split('\n'));for(const item of items)if(item.type==='function')parser.defineFunction(item.name,item.args,item.body);const scope=new GMLArchitectureScope('portable',parser,webglRunner),x=new Tensor(new Float32Array([1,2]),[1,2],false,'portable_x',webglRunner);parser.trackTensor(x);await scope.run('portable_block',x);const model=scope.model('portable_block'),buf=GMLModelBundle.encode(model,source);const restored=GMLModelBundle.restore(buf,webglRunner,parser);if(!(restored instanceof GMLCustomArchitectureModel)||restored.parameters().length!==model.parameters().length)throw new Error('Custom model did not restore bundled functions/parameters.');restored.parameters().forEach(t=>t.destroy());model.parameters().forEach(t=>t.destroy());x.destroy();return 'Custom architecture bundles restore their source-defined block and named parameter tensors without executing unrelated source statements.';}
|
| 9412 |
+
},
|
| 9413 |
+
{
|
| 9414 |
+
name:'Serialized DType Roundtrip',
|
| 9415 |
+
async run(){const m=new GMLModel([2,2],'relu',webglRunner,parser,1337);m.f16();const buf16=GMLModelBundle.encode(m,'');const r16=GMLModelBundle.restore(buf16,webglRunner,parser);if(r16.dtype!=='f16')throw new Error(`f16 bundle restored as ${r16.dtype}.`);m.i8();const buf8=GMLModelBundle.encode(m,'');const d8=GMLModelBundle.decode(buf8);if(!d8.header.tensors.every(t=>t.dtype==='i8'&&t.quantization?.scale>0))throw new Error('int8 bundle omitted quantization metadata.');const r8=GMLModelBundle.restore(buf8,webglRunner,parser);if(r8.dtype!=='i8'||r8.parameters().some(t=>t.requires_grad))throw new Error('int8 restore contract failed.');m.parameters().forEach(p=>p.destroy());r16.parameters().forEach(p=>p.destroy());r8.parameters().forEach(p=>p.destroy());return 'f16 and int8 dtypes survive model serialization; int8 stores explicit quantization metadata.';}
|
| 9416 |
+
},
|
| 9417 |
+
{
|
| 9418 |
+
name:'Character LM Showcase Contract',
|
| 9419 |
+
async run(){const lm=new GMLCharLanguageModel('Hello world from gml!',parser);const out=lm.generate('Hello ',15);if(out!=='Hello world from gml!')throw new Error(`Char LM continuation mismatch: ${out}`);if(lm.context>8||lm.vocab.length<5)throw new Error('Char LM compiler produced an invalid context/vocabulary.');return `learn(text) deterministically continues the showcase sequence with context=${lm.context}.`;}
|
| 9420 |
+
},
|
| 9421 |
{
|
| 9422 |
name:'Public UI Hides Internal Benchmarking',
|
| 9423 |
async run(){if(document.getElementById('runBenchmarkButton')||document.getElementById('benchmarkSummary'))throw new Error('Internal benchmark controls leaked into the user UI.');const visible=document.body.innerText;if(/TensorFlow|TF\.js|release gate/i.test(visible))throw new Error('Internal benchmark/release wording leaked into visible UI.');return 'Internal performance/release tooling remains callable programmatically but is absent from the normal product UI.';}
|
|
|
|
| 9429 |
{ type: 'string', regex: /^"(?:\\.|[^"\\])*"/ },
|
| 9430 |
{ type: 'directive', regex: /^@(dev|help|quiet)\b/ },
|
| 9431 |
{ type: 'keyword', regex: /^\b(function|return|if|else|for|while|true|false|null)\b/ }, // Added null
|
| 9432 |
+
{ type: 'builtin', regex: /^\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\.\s*(grad|backward|relu|relu6|leaky_relu|elu|selu|gelu|silu|swish|swiglu|geglu|softplus|mish|sigmoid|tanh|softmax|log_softmax|layer_norm|rms_norm|l2_norm|rope|causal_mask|window_mask|shift|causal_conv1d|affine_scan|repeat_interleave|patchify|frame|permute|batched_matmul|slice|concat|dropout|sum|mean|square|abs|exp|log|sqrt|transpose|reshape|zero_grad|fit|predict|evaluate|classify|summary|parameters|explain|generate|save|to|cpu|gpu|f32|f16|i8|run|repeat|model|step|wait|text|animation|anim|on|once|off|emit|when|done|error|clear|start|update|title|json|lines)\b/ },
|
| 9433 |
+
{ type: 'builtin', regex: /^\b(seed|random|int|float|string|bool|type|image|audio|video|patchify|frame|tensor|param|mlp|architecture|plan|learn|fit|predict|evaluate|classify|generate|save|mse|binary_cross_entropy|bce|cross_entropy|assert|assert_shape|assert_finite|describe|add|sub|mul|div|square|abs|negate|transpose|permute|matmul|batched_matmul|linear|relu|relu6|leaky_relu|elu|selu|gelu|silu|swish|swiglu|geglu|softplus|mish|sigmoid|tanh|softmax|log_softmax|layer_norm|rms_norm|l2_norm|rope|causal_mask|window_mask|attention|shift|causal_conv1d|affine_scan|repeat_interleave|concat|slice|one_hot|embedding|lerp|dropout|exp|log|sqrt|sum|mean|normalize|flatten|reshape|zeros|ones|series|history|plot|dots|progress|watch|emit|clear_live|write|append|read|files|remove|grid|table|md|markdown|graphemes|text_length|step|zero_grad|print|print_grad|has_grad|_clear_grad|_set_grad)\b/ },
|
| 9434 |
{ type: 'number', regex: /^(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+\-]?\d+)?\b/ },
|
| 9435 |
{ type: 'operator', regex: /^(==|<=|>=|!=|&&|\|\|)/ },
|
| 9436 |
{ type: 'operator', regex: /^[+\-*/=<>^]/ },
|
|
|
|
| 11426 |
}
|
| 11427 |
|
| 11428 |
function highlightSyntax(code) {
|
| 11429 |
+
const userFunctions=new Set();
|
| 11430 |
+
for(const match of String(code).matchAll(/\bfunction\s+([A-Za-z_$][\w$]*)\s*\(/g))userFunctions.add(match[1]);
|
| 11431 |
let remainingCode = code;
|
| 11432 |
let htmlOutput = '';
|
| 11433 |
|
|
|
|
| 11442 |
if (tokenDef.type === 'whitespace' || tokenDef.type === 'unknown') {
|
| 11443 |
htmlOutput += escapeHtml(tokenValue);
|
| 11444 |
} else {
|
| 11445 |
+
const bare=tokenValue.trim();
|
| 11446 |
+
const actualType=(tokenDef.type==='builtin'&&userFunctions.has(bare))?'identifier':tokenDef.type;
|
| 11447 |
+
htmlOutput += `<span class="syntax-${actualType}">${escapeHtml(tokenValue)}</span>`;
|
| 11448 |
}
|
| 11449 |
matched = true;
|
| 11450 |
break;
|
|
|
|
| 11803 |
const caretPos = codeInput.selectionDirection === 'backward' ? codeInput.selectionStart : codeInput.selectionEnd;
|
| 11804 |
|
| 11805 |
let left = paddingLeft;
|
| 11806 |
+
let top = paddingTop + 7;
|
| 11807 |
let width = caretSize;
|
| 11808 |
let height = caretSize;
|
| 11809 |
let borderRadius = '50%';
|
|
|
|
| 11825 |
|
| 11826 |
const atLineEnd = caretPos === codeInput.value.length || codeInput.value.charAt(caretPos) === '\n';
|
| 11827 |
if (atLineEnd) {
|
| 11828 |
+
top += 5;
|
| 11829 |
} else {
|
| 11830 |
width = 2;
|
| 11831 |
height = Math.max(14, Math.round(lineHeight * 0.95) - 2);
|