appvoid commited on
Commit
ef604a8
·
verified ·
1 Parent(s): 7afa87e

Upload index.html

Browse files
Files changed (1) hide show
  1. 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 if (!this.gl.getExtension('EXT_color_buffer_float')) {
1288
- console.warn('No EXT_color_buffer_float');
 
 
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 a Float32Array or null.');
1442
- if(d.length!==w*h*4) throw new GMLValidationError(`Texture upload requires ${w*h*4} float values, got ${d.length}.`);
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 Float32Array. Got ${data === null ? 'null' : typeof data}.`);
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
- this.data=data;
1854
- }else if(Array.isArray(data)){
1855
- if(data.length!==this.size)throw Error(`Data array length ${data.length} mismatch with shape product ${this.size} for ${this.name}`);
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, Array, or null.`);
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 p=this.runner._padDataForTexture(this.data,this.texWidth,this.texHeight);
 
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
- this.data=this.runner.readTexture(this);
1938
- if(this.data===null){console.error(`Failed to read GPU data for ${this.name}.`);return null;}
 
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}, sh=[${this.shape.join(',')}]${d}, grad=${gradStatus}${t})`;
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 output=this.forward(ctx,...inputs);
 
 
 
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.__gmlProperties = new Set(['inputSize', 'outputSize', 'activation', 'task', 'history', 'plan', 'inputEncoding']);
4092
- this.__gmlMethods = new Set(['fit', 'predict', 'evaluate', 'summary', 'parameters', 'explain']);
 
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.texture?'gpu':'cpu'}), bias: Tensor(shape=[${layer.B.shape.join(', ')}], device=${layer.B.texture?'gpu':'cpu'}) }${index===this.layers.length-1?'':','}`);
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)', 'Builds and trains a model from examples and targets.'],
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(MatMul.apply(ctx, a, b));
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?)", "Usually just write learn(X, Y). Optional intent: 'fast', 'accurate', 'tiny', or 'research'. Legacy hidden/epoch/lr overrides still work.");
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: 'emoji',
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{const raw=storage.getItem(GML_WORKSPACE_KEY);if(!raw)return false;state=JSON.parse(raw);}catch(_){return false;}
7893
- if(!state||typeof state.source!=='string')return false;
 
 
 
 
 
 
 
 
 
 
 
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: 'Intent-first ML, inferred tensors, autograd, and GPU-resident training.',
8051
- code: `@dev
8052
- # Arithmetic
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
- function softmax_1d(logits_vector) {
8094
- exp_logits = exp(logits_vector)
8095
- return exp_logits / sum(exp_logits)
8096
- }
8097
 
8098
- function cross_entropy_1d(probabilities, target_one_hot) {
8099
- return negate(sum(target_one_hot * log(probabilities)))
 
8100
  }
8101
 
8102
- W = param([[0.1, 0.2, 0.3], [-0.1, 0.5, 0.2]])
8103
- B = param([[0.01, -0.02, 0.03]])
8104
- Input = [[0.5, -0.5]]
8105
- Target = [[0., 1., 0.]]
8106
- Loss = cross_entropy_1d(softmax_1d(linear(Input, W, B)), Target)
8107
- print "Loss:", Loss
8108
- Loss.backward()
8109
- step(0.1, W, B)
8110
- print "Updated W:", W
8111
- print "Updated B:", B
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
- htmlOutput += `<span class="syntax-${tokenDef.type}">${escapeHtml(tokenValue)}</span>`;
 
 
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 + 8;
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 += 6;
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);