@danielsimonjr/mathts-matrix 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +391 -804
  2. package/dist/index.js +1740 -2359
  3. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -2954,572 +2954,18 @@ function createWASMBackend(config) {
2954
2954
  // src/backends/GPUMatrixBackend.ts
2955
2955
  init_DenseMatrix();
2956
2956
 
2957
- // src/backends/gpu/detect.ts
2958
- var NO_WEBGPU_CAPABILITIES = {
2959
- supported: false,
2960
- adapterInfo: null,
2961
- maxBufferSize: 0,
2962
- maxWorkgroupSize: [0, 0, 0],
2963
- maxStorageBufferBindingSize: 0,
2964
- maxComputeInvocationsPerWorkgroup: 0,
2965
- maxComputeWorkgroupsPerDimension: 0,
2966
- isFallbackAdapter: false,
2967
- features: []
2968
- };
2969
- function hasWebGPU() {
2970
- if (typeof navigator === "undefined") {
2971
- return false;
2972
- }
2973
- return "gpu" in navigator;
2974
- }
2975
- async function getGPUAdapter(options) {
2976
- if (!hasWebGPU()) {
2977
- return null;
2978
- }
2979
- try {
2980
- const gpu = navigator.gpu;
2981
- const adapter = await gpu.requestAdapter(options);
2982
- return adapter;
2983
- } catch {
2984
- return null;
2985
- }
2986
- }
2987
- async function detectGPUCapabilities(preferHighPerformance = true) {
2988
- if (!hasWebGPU()) {
2989
- return NO_WEBGPU_CAPABILITIES;
2990
- }
2991
- try {
2992
- const gpu = navigator.gpu;
2993
- const adapter = await gpu.requestAdapter({
2994
- powerPreference: preferHighPerformance ? "high-performance" : "low-power"
2995
- });
2996
- if (!adapter) {
2997
- return NO_WEBGPU_CAPABILITIES;
2998
- }
2999
- const info = adapter.info || {};
3000
- const adapterInfo = {
3001
- vendor: info.vendor || "unknown",
3002
- architecture: info.architecture || "unknown",
3003
- device: info.device || "unknown",
3004
- description: info.description || "unknown"
3005
- };
3006
- const limits = adapter.limits;
3007
- const features = Array.from(adapter.features);
3008
- return {
3009
- supported: true,
3010
- adapterInfo,
3011
- maxBufferSize: limits.maxBufferSize,
3012
- maxWorkgroupSize: [
3013
- limits.maxComputeWorkgroupSizeX,
3014
- limits.maxComputeWorkgroupSizeY,
3015
- limits.maxComputeWorkgroupSizeZ
3016
- ],
3017
- maxStorageBufferBindingSize: limits.maxStorageBufferBindingSize,
3018
- maxComputeInvocationsPerWorkgroup: limits.maxComputeInvocationsPerWorkgroup,
3019
- maxComputeWorkgroupsPerDimension: limits.maxComputeWorkgroupsPerDimension,
3020
- isFallbackAdapter: adapter.isFallbackAdapter ?? false,
3021
- features
3022
- };
3023
- } catch {
3024
- return NO_WEBGPU_CAPABILITIES;
3025
- }
3026
- }
3027
- function getRecommendedWorkgroupSize(capabilities) {
3028
- if (!capabilities.supported) {
3029
- return [1, 1, 1];
3030
- }
3031
- const preferred = [16, 16, 1];
3032
- const maxX = capabilities.maxWorkgroupSize[0];
3033
- const maxY = capabilities.maxWorkgroupSize[1];
3034
- const maxTotal = capabilities.maxComputeInvocationsPerWorkgroup;
3035
- let x = Math.min(preferred[0], maxX);
3036
- let y = Math.min(preferred[1], maxY);
3037
- while (x * y > maxTotal) {
3038
- if (x > y) {
3039
- x = Math.floor(x / 2);
3040
- } else {
3041
- y = Math.floor(y / 2);
3042
- }
3043
- }
3044
- return [x, y, 1];
3045
- }
3046
-
3047
- // src/backends/gpu/GPUContext.ts
3048
- var GPUContext = class {
3049
- adapter = null;
3050
- device = null;
3051
- _status = "uninitialized";
3052
- _capabilities = null;
3053
- _lastError = null;
3054
- deviceLostCallbacks = [];
3055
- label;
3056
- constructor(options = {}) {
3057
- this.label = options.label || "MathTS-GPUContext";
3058
- }
3059
- /**
3060
- * Get the current status
3061
- */
3062
- get status() {
3063
- return this._status;
3064
- }
3065
- /**
3066
- * Check if context is ready
3067
- */
3068
- get isReady() {
3069
- return this._status === "ready" && this.device !== null;
3070
- }
3071
- /**
3072
- * Get the GPU device (throws if not initialized)
3073
- */
3074
- getDevice() {
3075
- if (!this.device) {
3076
- throw new Error("GPUContext not initialized. Call initialize() first.");
3077
- }
3078
- return this.device;
3079
- }
3080
- /**
3081
- * Get the GPU queue
3082
- */
3083
- getQueue() {
3084
- return this.getDevice().queue;
3085
- }
3086
- /**
3087
- * Get capabilities
3088
- */
3089
- get capabilities() {
3090
- return this._capabilities;
3091
- }
3092
- /**
3093
- * Get last error
3094
- */
3095
- get lastError() {
3096
- return this._lastError;
3097
- }
3098
- /**
3099
- * Initialize the GPU context
3100
- */
3101
- async initialize(options = {}) {
3102
- if (this._status === "ready") {
3103
- return true;
3104
- }
3105
- if (this._status === "initializing") {
3106
- throw new Error("Already initializing");
3107
- }
3108
- this._status = "initializing";
3109
- try {
3110
- if (!hasWebGPU()) {
3111
- throw new Error("WebGPU is not supported in this environment");
3112
- }
3113
- this._capabilities = await detectGPUCapabilities(options.preferHighPerformance ?? true);
3114
- if (!this._capabilities.supported) {
3115
- throw new Error("WebGPU adapter not available");
3116
- }
3117
- this.adapter = await getGPUAdapter({
3118
- powerPreference: options.preferHighPerformance ? "high-performance" : "low-power"
3119
- });
3120
- if (!this.adapter) {
3121
- throw new Error("Failed to get GPU adapter");
3122
- }
3123
- const deviceDescriptor = {
3124
- label: this.label,
3125
- requiredFeatures: options.requiredFeatures || [],
3126
- requiredLimits: options.requiredLimits || {}
3127
- };
3128
- this.device = await this.adapter.requestDevice(deviceDescriptor);
3129
- if (!this.device) {
3130
- throw new Error("Failed to get GPU device");
3131
- }
3132
- this.device.lost.then((info) => {
3133
- this._status = "lost";
3134
- const event = {
3135
- reason: info.reason,
3136
- message: info.message
3137
- };
3138
- this.deviceLostCallbacks.forEach((cb) => cb(event));
3139
- });
3140
- this.device.onuncapturederror = (event) => {
3141
- console.error("GPU uncaptured error:", event.error);
3142
- this._lastError = new Error(`GPU Error: ${event.error.message}`);
3143
- };
3144
- this._status = "ready";
3145
- return true;
3146
- } catch (error) {
3147
- this._status = "error";
3148
- this._lastError = error;
3149
- return false;
3150
- }
3151
- }
3152
- /**
3153
- * Register callback for device lost event
3154
- */
3155
- onDeviceLost(callback) {
3156
- this.deviceLostCallbacks.push(callback);
3157
- }
3158
- /**
3159
- * Create a command encoder
3160
- */
3161
- createCommandEncoder(label) {
3162
- return this.getDevice().createCommandEncoder({
3163
- label: label || `${this.label}-CommandEncoder`
3164
- });
3165
- }
3166
- /**
3167
- * Create a buffer
3168
- */
3169
- createBuffer(size2, usage, label, mappedAtCreation = false) {
3170
- return this.getDevice().createBuffer({
3171
- label: label || `${this.label}-Buffer`,
3172
- size: size2,
3173
- usage,
3174
- mappedAtCreation
3175
- });
3176
- }
3177
- /**
3178
- * Create a storage buffer for compute operations
3179
- */
3180
- createStorageBuffer(size2, label, readable = true, writable = true) {
3181
- let usage = GPUBufferUsage.STORAGE;
3182
- if (readable) {
3183
- usage |= GPUBufferUsage.COPY_SRC;
3184
- }
3185
- if (writable) {
3186
- usage |= GPUBufferUsage.COPY_DST;
3187
- }
3188
- return this.createBuffer(size2, usage, label);
3189
- }
3190
- /**
3191
- * Create a staging buffer for reading back data
3192
- */
3193
- createStagingBuffer(size2, label) {
3194
- return this.createBuffer(size2, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, label);
3195
- }
3196
- /**
3197
- * Create a compute pipeline
3198
- */
3199
- createComputePipeline(shaderModule, entryPoint, layout, label) {
3200
- return this.getDevice().createComputePipeline({
3201
- label: label || `${this.label}-ComputePipeline`,
3202
- layout: layout || "auto",
3203
- compute: {
3204
- module: shaderModule,
3205
- entryPoint
3206
- }
3207
- });
3208
- }
3209
- /**
3210
- * Create a shader module from WGSL source
3211
- */
3212
- createShaderModule(code, label) {
3213
- return this.getDevice().createShaderModule({
3214
- label: label || `${this.label}-ShaderModule`,
3215
- code
3216
- });
3217
- }
3218
- /**
3219
- * Create a bind group
3220
- */
3221
- createBindGroup(layout, entries, label) {
3222
- return this.getDevice().createBindGroup({
3223
- label: label || `${this.label}-BindGroup`,
3224
- layout,
3225
- entries
3226
- });
3227
- }
3228
- /**
3229
- * Submit commands to the GPU queue
3230
- */
3231
- submitCommands(commandBuffers) {
3232
- this.getQueue().submit(commandBuffers);
3233
- }
3234
- /**
3235
- * Write data to a buffer
3236
- */
3237
- writeBuffer(buffer, data, bufferOffset = 0, dataOffset, size2) {
3238
- this.getQueue().writeBuffer(buffer, bufferOffset, data, dataOffset, size2);
3239
- }
3240
- /**
3241
- * Read data from a buffer (async)
3242
- */
3243
- async readBuffer(buffer, offset = 0, size2) {
3244
- const readSize = size2 ?? buffer.size - offset;
3245
- const stagingBuffer = this.createStagingBuffer(readSize);
3246
- const encoder = this.createCommandEncoder();
3247
- encoder.copyBufferToBuffer(buffer, offset, stagingBuffer, 0, readSize);
3248
- this.submitCommands([encoder.finish()]);
3249
- await stagingBuffer.mapAsync(GPUMapMode.READ);
3250
- const data = stagingBuffer.getMappedRange().slice(0);
3251
- stagingBuffer.unmap();
3252
- stagingBuffer.destroy();
3253
- return data;
3254
- }
3255
- /**
3256
- * Dispatch a compute shader
3257
- */
3258
- dispatchCompute(pipeline, bindGroups, workgroupCounts) {
3259
- const encoder = this.createCommandEncoder();
3260
- const pass = encoder.beginComputePass();
3261
- pass.setPipeline(pipeline);
3262
- bindGroups.forEach((bg, i) => pass.setBindGroup(i, bg));
3263
- pass.dispatchWorkgroups(...workgroupCounts);
3264
- pass.end();
3265
- this.submitCommands([encoder.finish()]);
3266
- }
3267
- /**
3268
- * Wait for all GPU operations to complete
3269
- */
3270
- async waitForCompletion() {
3271
- await this.getQueue().onSubmittedWorkDone();
3272
- }
3273
- /**
3274
- * Destroy the context and release resources
3275
- */
3276
- destroy() {
3277
- if (this.device) {
3278
- this.device.destroy();
3279
- this.device = null;
3280
- }
3281
- this.adapter = null;
3282
- this._status = "uninitialized";
3283
- this._capabilities = null;
3284
- this.deviceLostCallbacks = [];
3285
- }
3286
- };
3287
- var globalContext = null;
3288
- function getGlobalGPUContext() {
3289
- if (!globalContext) {
3290
- globalContext = new GPUContext({ label: "MathTS-Global" });
3291
- }
3292
- return globalContext;
3293
- }
3294
- function destroyGlobalGPU() {
3295
- if (globalContext) {
3296
- globalContext.destroy();
3297
- globalContext = null;
3298
- }
3299
- }
3300
-
3301
- // src/backends/gpu/BufferPool.ts
3302
- var DEFAULT_MAX_CACHE_SIZE = 512 * 1024 * 1024;
3303
- var DEFAULT_EVICTION_TIMEOUT = 3e4;
3304
- var DEFAULT_EVICTION_INTERVAL = 1e4;
3305
- var BufferPool = class {
3306
- context;
3307
- buffers = /* @__PURE__ */ new Map();
3308
- maxCacheSize;
3309
- evictionTimeout;
3310
- evictionTimer = null;
3311
- currentCacheSize = 0;
3312
- constructor(context, options = {}) {
3313
- this.context = context;
3314
- this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;
3315
- this.evictionTimeout = options.evictionTimeout ?? DEFAULT_EVICTION_TIMEOUT;
3316
- if (options.autoEvict !== false) {
3317
- this.startAutoEviction(options.evictionInterval ?? DEFAULT_EVICTION_INTERVAL);
3318
- }
3319
- }
3320
- /**
3321
- * Generate a key for buffer categorization
3322
- */
3323
- getBufferKey(size2, usage) {
3324
- const roundedSize = this.roundUpToPowerOf2(size2);
3325
- return `${roundedSize}_${usage}`;
3326
- }
3327
- /**
3328
- * Round up to nearest power of 2
3329
- */
3330
- roundUpToPowerOf2(n) {
3331
- if (n <= 0) return 1;
3332
- let power = 1;
3333
- while (power < n && power < Number.MAX_SAFE_INTEGER) {
3334
- power *= 2;
3335
- }
3336
- return power;
3337
- }
3338
- /**
3339
- * Acquire a buffer from the pool or create a new one
3340
- */
3341
- acquire(size2, usage, label) {
3342
- const key = this.getBufferKey(size2, usage);
3343
- const entries = this.buffers.get(key);
3344
- if (entries) {
3345
- for (const entry2 of entries) {
3346
- if (!entry2.inUse && entry2.size >= size2) {
3347
- entry2.inUse = true;
3348
- entry2.lastUsed = Date.now();
3349
- entry2.label = label;
3350
- return entry2.buffer;
3351
- }
3352
- }
3353
- }
3354
- const roundedSize = this.roundUpToPowerOf2(size2);
3355
- const buffer = this.context.createBuffer(roundedSize, usage, label);
3356
- const entry = {
3357
- buffer,
3358
- size: roundedSize,
3359
- usage,
3360
- inUse: true,
3361
- lastUsed: Date.now(),
3362
- label
3363
- };
3364
- if (!this.buffers.has(key)) {
3365
- this.buffers.set(key, []);
3366
- }
3367
- this.buffers.get(key).push(entry);
3368
- this.currentCacheSize += roundedSize;
3369
- if (this.currentCacheSize > this.maxCacheSize) {
3370
- this.evictOldBuffers();
3371
- }
3372
- return buffer;
3373
- }
3374
- /**
3375
- * Release a buffer back to the pool
3376
- */
3377
- release(buffer) {
3378
- for (const entries of this.buffers.values()) {
3379
- for (const entry of entries) {
3380
- if (entry.buffer === buffer) {
3381
- entry.inUse = false;
3382
- entry.lastUsed = Date.now();
3383
- return;
3384
- }
3385
- }
3386
- }
3387
- }
3388
- /**
3389
- * Create a storage buffer from the pool
3390
- */
3391
- acquireStorageBuffer(size2, label, readable = true, writable = true) {
3392
- let usage = GPUBufferUsage.STORAGE;
3393
- if (readable) {
3394
- usage |= GPUBufferUsage.COPY_SRC;
3395
- }
3396
- if (writable) {
3397
- usage |= GPUBufferUsage.COPY_DST;
3398
- }
3399
- return this.acquire(size2, usage, label);
3400
- }
3401
- /**
3402
- * Create a staging buffer from the pool
3403
- */
3404
- acquireStagingBuffer(size2, label) {
3405
- return this.acquire(size2, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, label);
3406
- }
3407
- /**
3408
- * Create a uniform buffer from the pool
3409
- */
3410
- acquireUniformBuffer(size2, label) {
3411
- return this.acquire(size2, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, label);
3412
- }
3413
- /**
3414
- * Evict old unused buffers
3415
- */
3416
- evictOldBuffers() {
3417
- const now = Date.now();
3418
- const timeout = this.evictionTimeout;
3419
- for (const [key, entries] of this.buffers.entries()) {
3420
- const remaining = [];
3421
- for (const entry of entries) {
3422
- if (!entry.inUse && now - entry.lastUsed > timeout) {
3423
- entry.buffer.destroy();
3424
- this.currentCacheSize -= entry.size;
3425
- } else {
3426
- remaining.push(entry);
3427
- }
3428
- }
3429
- if (remaining.length === 0) {
3430
- this.buffers.delete(key);
3431
- } else {
3432
- this.buffers.set(key, remaining);
3433
- }
3434
- }
3435
- }
3436
- /**
3437
- * Force eviction to reduce cache to target size
3438
- */
3439
- evictToSize(targetSize) {
3440
- if (this.currentCacheSize <= targetSize) {
3441
- return;
3442
- }
3443
- const allEntries = [];
3444
- for (const [key, entries] of this.buffers.entries()) {
3445
- entries.forEach((entry, index) => {
3446
- if (!entry.inUse) {
3447
- allEntries.push({ key, entry, index });
3448
- }
3449
- });
3450
- }
3451
- allEntries.sort((a, b) => a.entry.lastUsed - b.entry.lastUsed);
3452
- for (const { key, entry, index } of allEntries) {
3453
- if (this.currentCacheSize <= targetSize) {
3454
- break;
3455
- }
3456
- const entries = this.buffers.get(key);
3457
- entry.buffer.destroy();
3458
- this.currentCacheSize -= entry.size;
3459
- entries.splice(index, 1);
3460
- if (entries.length === 0) {
3461
- this.buffers.delete(key);
3462
- }
3463
- }
3464
- }
3465
- /**
3466
- * Start automatic eviction timer
3467
- */
3468
- startAutoEviction(interval) {
3469
- this.stopAutoEviction();
3470
- this.evictionTimer = setInterval(() => {
3471
- this.evictOldBuffers();
3472
- }, interval);
3473
- }
3474
- /**
3475
- * Stop automatic eviction timer
3476
- */
3477
- stopAutoEviction() {
3478
- if (this.evictionTimer) {
3479
- clearInterval(this.evictionTimer);
3480
- this.evictionTimer = null;
3481
- }
3482
- }
3483
- /**
3484
- * Get pool statistics
3485
- */
3486
- getStats() {
3487
- let totalBuffers = 0;
3488
- let inUseBuffers = 0;
3489
- for (const entries of this.buffers.values()) {
3490
- totalBuffers += entries.length;
3491
- inUseBuffers += entries.filter((e) => e.inUse).length;
3492
- }
3493
- return {
3494
- totalBuffers,
3495
- inUseBuffers,
3496
- cachedBuffers: totalBuffers - inUseBuffers,
3497
- currentCacheSize: this.currentCacheSize,
3498
- maxCacheSize: this.maxCacheSize
3499
- };
3500
- }
3501
- /**
3502
- * Clear all buffers and reset pool
3503
- */
3504
- clear() {
3505
- this.stopAutoEviction();
3506
- for (const entries of this.buffers.values()) {
3507
- for (const entry of entries) {
3508
- entry.buffer.destroy();
3509
- }
3510
- }
3511
- this.buffers.clear();
3512
- this.currentCacheSize = 0;
3513
- }
3514
- /**
3515
- * Destroy the pool
3516
- */
3517
- destroy() {
3518
- this.clear();
3519
- }
3520
- };
2957
+ // src/backends/GPUBackend.ts
2958
+ import {
2959
+ GPUContext,
2960
+ getGlobalGPUContext,
2961
+ BufferPool,
2962
+ ShaderManager,
2963
+ hasWebGPU,
2964
+ detectGPUCapabilities,
2965
+ getRecommendedWorkgroupSize
2966
+ } from "@danielsimonjr/mathts-gpu";
3521
2967
 
3522
- // src/backends/gpu/ShaderManager.ts
2968
+ // src/backends/gpu/builtin-shaders.ts
3523
2969
  var BUILTIN_SHADERS = {
3524
2970
  /** Matrix addition shader */
3525
2971
  matrixAdd: `
@@ -3647,7 +3093,11 @@ var BUILTIN_SHADERS = {
3647
3093
  @group(0) @binding(1) var<storage, read_write> output: array<f32>;
3648
3094
  @group(0) @binding(2) var<uniform> params: vec4<u32>; // inputLength, outputLength, _, _
3649
3095
 
3650
- var<workgroup> shared: array<f32, 256>;
3096
+ // NOTE: 'shared' is a RESERVED KEYWORD in WGSL \u2014 naming this workgroup
3097
+ // array 'shared' made this shader fail to compile, which (because
3098
+ // GPUBackend.initialize() precompiles every registered shader) poisoned
3099
+ // backend init and silently forced ALL GPU ops onto the CPU fallback.
3100
+ var<workgroup> sdata: array<f32, 256>;
3651
3101
 
3652
3102
  @compute @workgroup_size(256)
3653
3103
  fn main(
@@ -3665,2172 +3115,2103 @@ var BUILTIN_SHADERS = {
3665
3115
  if (idx + 256u < inputLength) {
3666
3116
  sum = sum + input[idx + 256u];
3667
3117
  }
3668
- shared[lid.x] = sum;
3118
+ sdata[lid.x] = sum;
3669
3119
 
3670
3120
  workgroupBarrier();
3671
3121
 
3672
3122
  // Reduce within workgroup
3673
3123
  for (var s: u32 = 128u; s > 0u; s = s >> 1u) {
3674
3124
  if (lid.x < s) {
3675
- shared[lid.x] = shared[lid.x] + shared[lid.x + s];
3125
+ sdata[lid.x] = sdata[lid.x] + sdata[lid.x + s];
3676
3126
  }
3677
3127
  workgroupBarrier();
3678
3128
  }
3679
3129
 
3680
3130
  // Write result
3681
3131
  if (lid.x == 0u) {
3682
- output[wid.x] = shared[0];
3132
+ output[wid.x] = sdata[0];
3683
3133
  }
3684
3134
  }
3685
3135
  `
3686
3136
  };
3687
- var ShaderManager = class {
3688
- context;
3689
- cache = /* @__PURE__ */ new Map();
3690
- constructor(context) {
3691
- this.context = context;
3692
- }
3693
- /**
3694
- * Get or compile a shader module
3695
- */
3696
- getShaderModule(name, code) {
3697
- let entry = this.cache.get(name);
3698
- if (entry) {
3699
- return entry.module;
3700
- }
3701
- const module = this.context.createShaderModule(code, name);
3702
- entry = {
3703
- module,
3704
- pipelines: /* @__PURE__ */ new Map(),
3705
- createdAt: Date.now()
3706
- };
3707
- this.cache.set(name, entry);
3708
- return module;
3137
+ function registerBuiltinShaders(sm) {
3138
+ for (const [name, code] of Object.entries(BUILTIN_SHADERS)) {
3139
+ sm.registerShader(name, code);
3709
3140
  }
3710
- /**
3711
- * Get a builtin shader module
3712
- */
3713
- getBuiltinShader(name) {
3714
- const code = BUILTIN_SHADERS[name];
3715
- if (!code) {
3716
- throw new Error(`Unknown builtin shader: ${name}`);
3717
- }
3718
- return this.getShaderModule(`builtin:${name}`, code);
3719
- }
3720
- /**
3721
- * Get or create a compute pipeline
3722
- */
3723
- getPipeline(shaderName, entryPoint, code, layout) {
3724
- const pipelineKey = `${shaderName}:${entryPoint}`;
3725
- let entry = this.cache.get(shaderName);
3726
- if (entry) {
3727
- const pipeline2 = entry.pipelines.get(pipelineKey);
3728
- if (pipeline2) {
3729
- return pipeline2;
3730
- }
3731
- } else if (code) {
3732
- this.getShaderModule(shaderName, code);
3733
- entry = this.cache.get(shaderName);
3734
- } else {
3735
- throw new Error(`Shader not found: ${shaderName}`);
3736
- }
3737
- const pipeline = this.context.createComputePipeline(
3738
- entry.module,
3739
- entryPoint,
3740
- layout,
3741
- pipelineKey
3742
- );
3743
- entry.pipelines.set(pipelineKey, pipeline);
3744
- return pipeline;
3141
+ }
3142
+
3143
+ // src/backends/GPUBackend.ts
3144
+ var DEFAULT_THRESHOLD = 256;
3145
+ var GPUBackend = class {
3146
+ context = null;
3147
+ bufferPool = null;
3148
+ shaderManager = null;
3149
+ _status = "uninitialized";
3150
+ _capabilities = null;
3151
+ _lastError = null;
3152
+ threshold;
3153
+ workgroupSize = [16, 16, 1];
3154
+ useGlobalContext;
3155
+ constructor(options = {}) {
3156
+ this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
3157
+ this.useGlobalContext = options.useGlobalContext ?? true;
3745
3158
  }
3746
3159
  /**
3747
- * Get a builtin compute pipeline
3160
+ * Get the current status
3748
3161
  */
3749
- getBuiltinPipeline(name, entryPoint = "main") {
3750
- const shaderName = `builtin:${name}`;
3751
- const code = BUILTIN_SHADERS[name];
3752
- return this.getPipeline(shaderName, entryPoint, code);
3162
+ get status() {
3163
+ return this._status;
3753
3164
  }
3754
3165
  /**
3755
- * Precompile all builtin shaders
3166
+ * Check if backend is ready
3756
3167
  */
3757
- precompileBuiltins() {
3758
- for (const name of Object.keys(BUILTIN_SHADERS)) {
3759
- this.getBuiltinShader(name);
3760
- this.getBuiltinPipeline(name);
3761
- }
3168
+ get isReady() {
3169
+ return this._status === "ready";
3762
3170
  }
3763
3171
  /**
3764
- * Clear shader cache
3172
+ * Get capabilities
3765
3173
  */
3766
- clearCache() {
3767
- this.cache.clear();
3174
+ get capabilities() {
3175
+ return this._capabilities;
3768
3176
  }
3769
3177
  /**
3770
- * Get cache statistics
3178
+ * Get last error
3771
3179
  */
3772
- getStats() {
3773
- let cachedShaders = 0;
3774
- let cachedPipelines = 0;
3775
- for (const entry of this.cache.values()) {
3776
- cachedShaders++;
3777
- cachedPipelines += entry.pipelines.size;
3778
- }
3779
- return { cachedShaders, cachedPipelines };
3180
+ get lastError() {
3181
+ return this._lastError;
3780
3182
  }
3781
- };
3782
-
3783
- // src/backends/gpu/BatchExecutor.ts
3784
- var BatchExecutor = class {
3785
- context;
3786
- shaders;
3787
- bufferPool;
3788
- operations = [];
3789
- options;
3790
3183
  /**
3791
- * Create a new batch executor
3184
+ * Initialize the GPU backend
3792
3185
  */
3793
- constructor(context, shaders, bufferPool, options = {}) {
3794
- this.context = context;
3795
- this.shaders = shaders;
3796
- this.bufferPool = bufferPool;
3797
- this.options = {
3798
- maxBatchSize: options.maxBatchSize ?? 100,
3799
- autoFlush: options.autoFlush ?? true,
3800
- waitForCompletion: options.waitForCompletion ?? true
3801
- };
3186
+ async initialize(options = {}) {
3187
+ if (this._status === "ready") {
3188
+ return true;
3189
+ }
3190
+ if (this._status === "initializing") {
3191
+ throw new Error("Already initializing");
3192
+ }
3193
+ this._status = "initializing";
3194
+ try {
3195
+ if (!hasWebGPU()) {
3196
+ this._status = "unsupported";
3197
+ this._lastError = new Error("WebGPU is not supported");
3198
+ return false;
3199
+ }
3200
+ this._capabilities = await detectGPUCapabilities(options.preferHighPerformance ?? true);
3201
+ if (!this._capabilities.supported) {
3202
+ this._status = "unsupported";
3203
+ this._lastError = new Error("WebGPU adapter not available");
3204
+ return false;
3205
+ }
3206
+ if (this.useGlobalContext) {
3207
+ this.context = getGlobalGPUContext();
3208
+ } else {
3209
+ this.context = new GPUContext({ label: "GPUBackend" });
3210
+ }
3211
+ const success = await this.context.initialize(options);
3212
+ if (!success) {
3213
+ this._status = "error";
3214
+ this._lastError = this.context.lastError;
3215
+ return false;
3216
+ }
3217
+ this.bufferPool = new BufferPool(this.context, options.bufferPoolOptions);
3218
+ this.shaderManager = new ShaderManager(this.context);
3219
+ registerBuiltinShaders(this.shaderManager);
3220
+ this.shaderManager.precompileRegistered();
3221
+ this.workgroupSize = getRecommendedWorkgroupSize(this._capabilities);
3222
+ this._status = "ready";
3223
+ return true;
3224
+ } catch (error) {
3225
+ this._status = "error";
3226
+ this._lastError = error;
3227
+ return false;
3228
+ }
3802
3229
  }
3803
3230
  /**
3804
- * Get current batch size
3231
+ * Check if GPU should be used for the given matrix size
3805
3232
  */
3806
- get size() {
3807
- return this.operations.length;
3233
+ shouldUseGPU(rows, cols) {
3234
+ if (!this.isReady) {
3235
+ return false;
3236
+ }
3237
+ return rows >= this.threshold || cols >= this.threshold;
3808
3238
  }
3809
3239
  /**
3810
- * Check if batch is empty
3240
+ * Calculate workgroup counts for a matrix
3811
3241
  */
3812
- get isEmpty() {
3813
- return this.operations.length === 0;
3242
+ calculateWorkgroups(rows, cols) {
3243
+ const [wgX, wgY] = this.workgroupSize;
3244
+ return [Math.ceil(cols / wgX), Math.ceil(rows / wgY), 1];
3814
3245
  }
3815
3246
  /**
3816
- * Check if batch is full
3247
+ * Get the GPU context
3817
3248
  */
3818
- get isFull() {
3819
- return this.operations.length >= this.options.maxBatchSize;
3249
+ getContext() {
3250
+ if (!this.context || !this.isReady) {
3251
+ throw new Error("GPUBackend not initialized");
3252
+ }
3253
+ return this.context;
3820
3254
  }
3821
3255
  /**
3822
- * Queue an add operation
3256
+ * Get the buffer pool
3823
3257
  */
3824
- add(inputA, inputB, output, dimensions) {
3825
- this.queueOperation({
3826
- type: "add",
3827
- inputA,
3828
- inputB,
3829
- output,
3830
- dimensions
3831
- });
3258
+ getBufferPool() {
3259
+ if (!this.bufferPool || !this.isReady) {
3260
+ throw new Error("GPUBackend not initialized");
3261
+ }
3262
+ return this.bufferPool;
3832
3263
  }
3833
3264
  /**
3834
- * Queue a subtract operation
3265
+ * Get the shader manager
3835
3266
  */
3836
- subtract(inputA, inputB, output, dimensions) {
3837
- this.queueOperation({
3838
- type: "subtract",
3839
- inputA,
3840
- inputB,
3841
- output,
3842
- dimensions
3843
- });
3267
+ getShaderManager() {
3268
+ if (!this.shaderManager || !this.isReady) {
3269
+ throw new Error("GPUBackend not initialized");
3270
+ }
3271
+ return this.shaderManager;
3844
3272
  }
3845
3273
  /**
3846
- * Queue an element-wise multiply operation
3274
+ * Add two matrices element-wise
3847
3275
  */
3848
- multiply(inputA, inputB, output, dimensions) {
3849
- this.queueOperation({
3850
- type: "multiply",
3851
- inputA,
3852
- inputB,
3853
- output,
3854
- dimensions
3855
- });
3276
+ async add(a, b, rows, cols) {
3277
+ const ctx = this.getContext();
3278
+ const pool = this.getBufferPool();
3279
+ const shaders = this.getShaderManager();
3280
+ const size2 = rows * cols * 4;
3281
+ const bufferA = pool.acquireStorageBuffer(size2, "matrix-a", true, true);
3282
+ const bufferB = pool.acquireStorageBuffer(size2, "matrix-b", true, true);
3283
+ const bufferResult = pool.acquireStorageBuffer(size2, "matrix-result", true, false);
3284
+ const bufferParams = pool.acquireUniformBuffer(16, "params");
3285
+ ctx.writeBuffer(bufferA, a);
3286
+ ctx.writeBuffer(bufferB, b);
3287
+ ctx.writeBuffer(bufferParams, new Uint32Array([rows, cols, 0, 0]));
3288
+ const pipeline = shaders.getRegisteredPipeline("matrixAdd");
3289
+ const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
3290
+ { binding: 0, resource: { buffer: bufferA } },
3291
+ { binding: 1, resource: { buffer: bufferB } },
3292
+ { binding: 2, resource: { buffer: bufferResult } },
3293
+ { binding: 3, resource: { buffer: bufferParams } }
3294
+ ]);
3295
+ ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(rows, cols));
3296
+ const resultData = await ctx.readBuffer(bufferResult);
3297
+ pool.release(bufferA);
3298
+ pool.release(bufferB);
3299
+ pool.release(bufferResult);
3300
+ pool.release(bufferParams);
3301
+ return new Float32Array(resultData);
3856
3302
  }
3857
3303
  /**
3858
- * Queue a scale operation
3304
+ * Multiply two matrices
3859
3305
  */
3860
- scale(input, output, scalar, dimensions) {
3861
- this.queueOperation({
3862
- type: "scale",
3863
- inputA: input,
3864
- output,
3865
- dimensions,
3866
- scalar
3867
- });
3306
+ async matmul(a, b, M, K, N) {
3307
+ const ctx = this.getContext();
3308
+ const pool = this.getBufferPool();
3309
+ const shaders = this.getShaderManager();
3310
+ const sizeA = M * K * 4;
3311
+ const sizeB = K * N * 4;
3312
+ const sizeResult = M * N * 4;
3313
+ const bufferA = pool.acquireStorageBuffer(sizeA, "matmul-a", true, true);
3314
+ const bufferB = pool.acquireStorageBuffer(sizeB, "matmul-b", true, true);
3315
+ const bufferResult = pool.acquireStorageBuffer(sizeResult, "matmul-result", true, false);
3316
+ const bufferParams = pool.acquireUniformBuffer(16, "params");
3317
+ ctx.writeBuffer(bufferA, a);
3318
+ ctx.writeBuffer(bufferB, b);
3319
+ ctx.writeBuffer(bufferParams, new Uint32Array([M, N, K, 0]));
3320
+ const pipeline = shaders.getRegisteredPipeline("matmul");
3321
+ const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
3322
+ { binding: 0, resource: { buffer: bufferA } },
3323
+ { binding: 1, resource: { buffer: bufferB } },
3324
+ { binding: 2, resource: { buffer: bufferResult } },
3325
+ { binding: 3, resource: { buffer: bufferParams } }
3326
+ ]);
3327
+ ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(M, N));
3328
+ const resultData = await ctx.readBuffer(bufferResult);
3329
+ pool.release(bufferA);
3330
+ pool.release(bufferB);
3331
+ pool.release(bufferResult);
3332
+ pool.release(bufferParams);
3333
+ return new Float32Array(resultData);
3868
3334
  }
3869
3335
  /**
3870
- * Queue a matrix multiplication operation
3336
+ * Transpose a matrix
3871
3337
  */
3872
- matmul(inputA, inputB, output, dimensions) {
3873
- this.queueOperation({
3874
- type: "matmul",
3875
- inputA,
3876
- inputB,
3877
- output,
3878
- dimensions
3879
- });
3338
+ async transpose(a, rows, cols) {
3339
+ const ctx = this.getContext();
3340
+ const pool = this.getBufferPool();
3341
+ const shaders = this.getShaderManager();
3342
+ const size2 = rows * cols * 4;
3343
+ const bufferA = pool.acquireStorageBuffer(size2, "transpose-a", true, true);
3344
+ const bufferResult = pool.acquireStorageBuffer(size2, "transpose-result", true, false);
3345
+ const bufferParams = pool.acquireUniformBuffer(16, "params");
3346
+ ctx.writeBuffer(bufferA, a);
3347
+ ctx.writeBuffer(bufferParams, new Uint32Array([rows, cols, 0, 0]));
3348
+ const pipeline = shaders.getRegisteredPipeline("transpose");
3349
+ const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
3350
+ { binding: 0, resource: { buffer: bufferA } },
3351
+ { binding: 1, resource: { buffer: bufferResult } },
3352
+ { binding: 2, resource: { buffer: bufferParams } }
3353
+ ]);
3354
+ ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(rows, cols));
3355
+ const resultData = await ctx.readBuffer(bufferResult);
3356
+ pool.release(bufferA);
3357
+ pool.release(bufferResult);
3358
+ pool.release(bufferParams);
3359
+ return new Float32Array(resultData);
3880
3360
  }
3881
3361
  /**
3882
- * Queue a transpose operation
3362
+ * Scale a matrix by a scalar
3883
3363
  */
3884
- transpose(input, output, dimensions) {
3885
- this.queueOperation({
3886
- type: "transpose",
3887
- inputA: input,
3888
- output,
3889
- dimensions
3890
- });
3364
+ async scale(a, scalar) {
3365
+ const ctx = this.getContext();
3366
+ const pool = this.getBufferPool();
3367
+ const shaders = this.getShaderManager();
3368
+ const size2 = a.length * 4;
3369
+ const bufferA = pool.acquireStorageBuffer(size2, "scale-a", true, true);
3370
+ const bufferResult = pool.acquireStorageBuffer(size2, "scale-result", true, false);
3371
+ const bufferParams = pool.acquireUniformBuffer(16, "params");
3372
+ ctx.writeBuffer(bufferA, a);
3373
+ ctx.writeBuffer(bufferParams, new Float32Array([scalar, a.length, 0, 0]));
3374
+ const pipeline = shaders.getRegisteredPipeline("scalarMul");
3375
+ const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
3376
+ { binding: 0, resource: { buffer: bufferA } },
3377
+ { binding: 1, resource: { buffer: bufferResult } },
3378
+ { binding: 2, resource: { buffer: bufferParams } }
3379
+ ]);
3380
+ const workgroups = Math.ceil(a.length / 256);
3381
+ ctx.dispatchCompute(pipeline, [bindGroup], [workgroups, 1, 1]);
3382
+ const resultData = await ctx.readBuffer(bufferResult);
3383
+ pool.release(bufferA);
3384
+ pool.release(bufferResult);
3385
+ pool.release(bufferParams);
3386
+ return new Float32Array(resultData);
3891
3387
  }
3892
3388
  /**
3893
- * Queue a sum reduction operation
3389
+ * Get backend statistics
3894
3390
  */
3895
- reduceSum(input, output, dimensions) {
3896
- this.queueOperation({
3897
- type: "reduce_sum",
3898
- inputA: input,
3899
- output,
3900
- dimensions
3901
- });
3391
+ getStats() {
3392
+ return {
3393
+ status: this._status,
3394
+ capabilities: this._capabilities,
3395
+ bufferPool: this.bufferPool?.getStats() || null,
3396
+ shaders: this.shaderManager?.getStats() || null
3397
+ };
3902
3398
  }
3903
3399
  /**
3904
- * Queue operation (internal)
3400
+ * Destroy the backend
3905
3401
  */
3906
- queueOperation(op) {
3907
- this.operations.push(op);
3908
- if (this.options.autoFlush && this.isFull) {
3909
- this.flushSync();
3402
+ destroy() {
3403
+ if (this.bufferPool) {
3404
+ this.bufferPool.destroy();
3405
+ this.bufferPool = null;
3406
+ }
3407
+ if (this.shaderManager) {
3408
+ this.shaderManager.clearCache();
3409
+ this.shaderManager = null;
3410
+ }
3411
+ if (this.context && !this.useGlobalContext) {
3412
+ this.context.destroy();
3910
3413
  }
3414
+ this.context = null;
3415
+ this._status = "uninitialized";
3416
+ }
3417
+ };
3418
+ var globalBackend = null;
3419
+ function getGlobalGPUBackend() {
3420
+ if (!globalBackend) {
3421
+ globalBackend = new GPUBackend({ useGlobalContext: true });
3422
+ }
3423
+ return globalBackend;
3424
+ }
3425
+ async function initializeGlobalGPUBackend(options) {
3426
+ const backend = getGlobalGPUBackend();
3427
+ return backend.initialize(options);
3428
+ }
3429
+ function destroyGlobalGPUBackend() {
3430
+ if (globalBackend) {
3431
+ globalBackend.destroy();
3432
+ globalBackend = null;
3433
+ }
3434
+ }
3435
+
3436
+ // src/backends/GPUMatrixBackend.ts
3437
+ import { hasWebGPU as hasWebGPU2, detectGPUCapabilities as detectGPUCapabilities2 } from "@danielsimonjr/mathts-gpu";
3438
+ var DEFAULT_CONFIG2 = {
3439
+ minElements: 65536,
3440
+ // 256x256
3441
+ useGlobalBackend: true,
3442
+ gpuOptions: {},
3443
+ fallbackOnError: true
3444
+ };
3445
+ var GPUMatrixBackend = class {
3446
+ type = "gpu";
3447
+ config;
3448
+ backend = null;
3449
+ capabilities = null;
3450
+ initPromise = null;
3451
+ _available = null;
3452
+ constructor(config = {}) {
3453
+ this.config = { ...DEFAULT_CONFIG2, ...config };
3911
3454
  }
3912
3455
  /**
3913
- * Flush all queued operations (async)
3456
+ * Check if GPU is available in the current environment
3914
3457
  */
3915
- async flush() {
3916
- if (this.isEmpty) {
3917
- return {
3918
- success: true,
3919
- operationCount: 0,
3920
- duration: 0
3921
- };
3922
- }
3923
- const start = performance.now();
3924
- const operationCount = this.operations.length;
3925
- try {
3926
- const encoder = this.context.createCommandEncoder();
3927
- for (const op of this.operations) {
3928
- this.encodeOperation(encoder, op);
3929
- }
3930
- const commandBuffer = encoder.finish();
3931
- this.context.submitCommands([commandBuffer]);
3932
- if (this.options.waitForCompletion) {
3933
- await this.context.getDevice().queue.onSubmittedWorkDone();
3934
- }
3935
- this.operations = [];
3936
- return {
3937
- success: true,
3938
- operationCount,
3939
- duration: performance.now() - start
3940
- };
3941
- } catch (error) {
3942
- return {
3943
- success: false,
3944
- operationCount,
3945
- duration: performance.now() - start,
3946
- error: error instanceof Error ? error.message : String(error)
3947
- };
3458
+ isAvailable() {
3459
+ if (this._available !== null) {
3460
+ return this._available;
3948
3461
  }
3462
+ this._available = hasWebGPU2();
3463
+ return this._available;
3949
3464
  }
3950
3465
  /**
3951
- * Flush synchronously (fire and forget)
3466
+ * Initialize the GPU backend
3952
3467
  */
3953
- flushSync() {
3954
- if (this.isEmpty) return;
3955
- try {
3956
- const encoder = this.context.createCommandEncoder();
3957
- for (const op of this.operations) {
3958
- this.encodeOperation(encoder, op);
3468
+ async initialize() {
3469
+ if (this.backend?.isReady) return;
3470
+ if (this.initPromise) return this.initPromise;
3471
+ this.initPromise = this.doInitialize();
3472
+ return this.initPromise;
3473
+ }
3474
+ async doInitialize() {
3475
+ if (!this.isAvailable()) {
3476
+ throw new Error("WebGPU is not available in this environment");
3477
+ }
3478
+ this.capabilities = await detectGPUCapabilities2(
3479
+ this.config.gpuOptions?.preferHighPerformance ?? true
3480
+ );
3481
+ if (!this.capabilities.supported) {
3482
+ throw new Error("WebGPU adapter not available");
3483
+ }
3484
+ if (this.config.useGlobalBackend) {
3485
+ const success = await initializeGlobalGPUBackend(this.config.gpuOptions);
3486
+ if (!success) {
3487
+ throw new Error("Failed to initialize global GPU backend");
3488
+ }
3489
+ this.backend = getGlobalGPUBackend();
3490
+ } else {
3491
+ this.backend = new GPUBackend(this.config.gpuOptions);
3492
+ const success = await this.backend.initialize(this.config.gpuOptions);
3493
+ if (!success) {
3494
+ throw new Error(`Failed to initialize GPU backend: ${this.backend.lastError?.message}`);
3959
3495
  }
3960
- const commandBuffer = encoder.finish();
3961
- this.context.submitCommands([commandBuffer]);
3962
- this.operations = [];
3963
- } catch {
3964
3496
  }
3965
3497
  }
3966
3498
  /**
3967
- * Encode a single operation into the command encoder
3499
+ * Check if operation should use GPU
3968
3500
  */
3969
- encodeOperation(encoder, op) {
3970
- const pipelineName = this.getPipelineName(op.type);
3971
- const pipeline = this.shaders.getBuiltinPipeline(pipelineName);
3972
- const entries = [{ binding: 0, resource: { buffer: op.inputA } }];
3973
- if (op.inputB) {
3974
- entries.push({ binding: 1, resource: { buffer: op.inputB } });
3975
- }
3976
- entries.push({
3977
- binding: op.inputB ? 2 : 1,
3978
- resource: { buffer: op.output }
3979
- });
3980
- const params = this.createParamsBuffer(op);
3981
- entries.push({
3982
- binding: entries.length,
3983
- resource: { buffer: params }
3984
- });
3985
- const bindGroup = this.context.getDevice().createBindGroup({
3986
- layout: pipeline.getBindGroupLayout(0),
3987
- entries
3988
- });
3989
- const passEncoder = encoder.beginComputePass();
3990
- passEncoder.setPipeline(pipeline);
3991
- passEncoder.setBindGroup(0, bindGroup);
3992
- const [wgX, wgY, wgZ] = this.calculateWorkgroups(op);
3993
- passEncoder.dispatchWorkgroups(wgX, wgY, wgZ);
3994
- passEncoder.end();
3501
+ shouldUseGPU(elementCount) {
3502
+ return this.backend !== null && this.backend.isReady && elementCount >= this.config.minElements;
3995
3503
  }
3996
3504
  /**
3997
- * Get pipeline name for operation type
3505
+ * Execute GPU operation with fallback
3998
3506
  */
3999
- getPipelineName(type) {
4000
- const mapping = {
4001
- add: "matrixAdd",
4002
- subtract: "matrixSub",
4003
- multiply: "matrixMul",
4004
- divide: "matrixDiv",
4005
- scale: "scalarMul",
4006
- matmul: "matmul",
4007
- transpose: "transpose",
4008
- reduce_sum: "sumReduce",
4009
- reduce_max: "maxReduce",
4010
- reduce_min: "minReduce"
4011
- };
4012
- return mapping[type] || "matrixAdd";
3507
+ async executeWithFallback(gpuOp, fallback) {
3508
+ if (!this.config.fallbackOnError) {
3509
+ return gpuOp();
3510
+ }
3511
+ try {
3512
+ return await gpuOp();
3513
+ } catch (error) {
3514
+ console.warn("GPU operation failed, falling back to JS:", error);
3515
+ return fallback();
3516
+ }
4013
3517
  }
4014
3518
  /**
4015
- * Create params buffer for operation
3519
+ * Get GPU capabilities
4016
3520
  */
4017
- createParamsBuffer(op) {
4018
- const { rows, cols, k } = op.dimensions;
4019
- let data;
4020
- if (op.type === "matmul" && k !== void 0) {
4021
- data = new Uint32Array([rows, cols, k, 0]);
4022
- } else if (op.type === "scale" && op.scalar !== void 0) {
4023
- const floatArray = new Float32Array([op.scalar]);
4024
- const uintView = new Uint32Array(floatArray.buffer);
4025
- data = new Uint32Array([rows, cols, uintView[0], 0]);
4026
- } else {
4027
- data = new Uint32Array([rows, cols, 0, 0]);
4028
- }
4029
- const buffer = this.bufferPool.acquireUniformBuffer(16, "batch-params");
4030
- this.context.writeBuffer(buffer, data);
4031
- return buffer;
3521
+ getCapabilities() {
3522
+ return this.capabilities;
4032
3523
  }
4033
3524
  /**
4034
- * Calculate workgroup dispatch counts
3525
+ * Get backend statistics
4035
3526
  */
4036
- calculateWorkgroups(op) {
4037
- const { rows, cols } = op.dimensions;
4038
- const workgroupSize = 16;
4039
- if (op.type === "reduce_sum" || op.type === "reduce_max" || op.type === "reduce_min") {
4040
- const total = rows * cols;
4041
- return [Math.ceil(total / 256), 1, 1];
3527
+ getStats() {
3528
+ return this.backend?.getStats() ?? null;
3529
+ }
3530
+ // =========================================================================
3531
+ // Element-wise Operations
3532
+ // =========================================================================
3533
+ add(a, b) {
3534
+ const elementCount = a.rows * a.cols;
3535
+ if (!this.shouldUseGPU(elementCount)) {
3536
+ return jsBackend.add(a, b);
4042
3537
  }
4043
- return [Math.ceil(cols / workgroupSize), Math.ceil(rows / workgroupSize), 1];
3538
+ return jsBackend.add(a, b);
4044
3539
  }
4045
3540
  /**
4046
- * Clear all queued operations without executing
3541
+ * Async add operation using GPU
4047
3542
  */
4048
- clear() {
4049
- this.operations = [];
3543
+ async addAsync(a, b) {
3544
+ const elementCount = a.rows * a.cols;
3545
+ if (!this.shouldUseGPU(elementCount)) {
3546
+ return jsBackend.add(a, b);
3547
+ }
3548
+ return this.executeWithFallback(
3549
+ async () => {
3550
+ const aData = new Float32Array(a.toFloat64Array());
3551
+ const bData = new Float32Array(b.toFloat64Array());
3552
+ const result = await this.backend.add(aData, bData, a.rows, a.cols);
3553
+ return DenseMatrix.fromFlat(a.rows, a.cols, Array.from(result));
3554
+ },
3555
+ () => jsBackend.add(a, b)
3556
+ );
4050
3557
  }
4051
- /**
4052
- * Get statistics about the batch executor
4053
- */
4054
- getStats() {
4055
- return {
4056
- queuedOperations: this.operations.length,
4057
- maxBatchSize: this.options.maxBatchSize,
4058
- autoFlush: this.options.autoFlush
4059
- };
3558
+ subtract(a, b) {
3559
+ const elementCount = a.rows * a.cols;
3560
+ if (!this.shouldUseGPU(elementCount)) {
3561
+ return jsBackend.subtract(a, b);
3562
+ }
3563
+ return jsBackend.subtract(a, b);
4060
3564
  }
4061
- };
4062
-
4063
- // src/backends/gpu/Sync.ts
4064
- var SyncManager = class {
4065
- context;
4066
- config;
4067
- pendingTransfers = /* @__PURE__ */ new Map();
4068
- nextRequestId = 0;
4069
- stagingBuffers = [];
4070
- // Statistics
4071
- totalUploads = 0;
4072
- totalDownloads = 0;
4073
- totalBytesUploaded = 0;
4074
- totalBytesDownloaded = 0;
4075
- constructor(context, _bufferPool, config = {}) {
4076
- this.context = context;
4077
- this.config = {
4078
- strategy: config.strategy ?? "lazy",
4079
- chunkSize: config.chunkSize ?? 1024 * 1024,
4080
- // 1MB default
4081
- maxPendingTransfers: config.maxPendingTransfers ?? 16,
4082
- coalesceTransfers: config.coalesceTransfers ?? true
4083
- };
3565
+ multiplyElementwise(a, b) {
3566
+ const elementCount = a.rows * a.cols;
3567
+ if (!this.shouldUseGPU(elementCount)) {
3568
+ return jsBackend.multiplyElementwise(a, b);
3569
+ }
3570
+ return jsBackend.multiplyElementwise(a, b);
4084
3571
  }
4085
- /**
4086
- * Upload data from CPU to GPU
4087
- */
4088
- async upload(cpuData, gpuBuffer, options = {}) {
4089
- const id = this.nextRequestId++;
4090
- const start = performance.now();
4091
- try {
4092
- const offset = options.offset ?? 0;
4093
- const size2 = options.size ?? cpuData.byteLength;
4094
- this.context.writeBuffer(gpuBuffer, cpuData, offset);
4095
- if (this.config.strategy === "immediate") {
4096
- await this.context.getDevice().queue.onSubmittedWorkDone();
4097
- }
4098
- this.totalUploads++;
4099
- this.totalBytesUploaded += size2;
4100
- return {
4101
- id,
4102
- success: true,
4103
- duration: performance.now() - start,
4104
- bytesTransferred: size2
4105
- };
4106
- } catch (error) {
4107
- return {
4108
- id,
4109
- success: false,
4110
- duration: performance.now() - start,
4111
- bytesTransferred: 0,
4112
- error: error instanceof Error ? error.message : String(error)
4113
- };
3572
+ divideElementwise(a, b) {
3573
+ const elementCount = a.rows * a.cols;
3574
+ if (!this.shouldUseGPU(elementCount)) {
3575
+ return jsBackend.divideElementwise(a, b);
4114
3576
  }
3577
+ return jsBackend.divideElementwise(a, b);
4115
3578
  }
4116
- /**
4117
- * Download data from GPU to CPU
4118
- */
4119
- async download(gpuBuffer, options = {}) {
4120
- const offset = options.offset ?? 0;
4121
- const size2 = options.size ?? gpuBuffer.size;
4122
- this.totalDownloads++;
4123
- this.totalBytesDownloaded += size2;
4124
- const buffer = await this.context.readBuffer(gpuBuffer, offset, size2);
4125
- return new Float32Array(buffer);
3579
+ scale(a, scalar) {
3580
+ const elementCount = a.rows * a.cols;
3581
+ if (!this.shouldUseGPU(elementCount)) {
3582
+ return jsBackend.scale(a, scalar);
3583
+ }
3584
+ return jsBackend.scale(a, scalar);
4126
3585
  }
4127
3586
  /**
4128
- * Download data using double-buffering for overlap
3587
+ * Async scale operation using GPU
4129
3588
  */
4130
- async downloadDoubleBuffered(gpuBuffer, size2) {
4131
- const staging1 = this.getOrCreateStagingBuffer(size2);
4132
- const encoder = this.context.createCommandEncoder();
4133
- encoder.copyBufferToBuffer(gpuBuffer, 0, staging1, 0, size2);
4134
- this.context.submitCommands([encoder.finish()]);
4135
- await staging1.mapAsync(GPUMapMode.READ);
4136
- const data = new Float32Array(staging1.getMappedRange().slice(0));
4137
- staging1.unmap();
4138
- return data;
3589
+ async scaleAsync(a, scalar) {
3590
+ const elementCount = a.rows * a.cols;
3591
+ if (!this.shouldUseGPU(elementCount)) {
3592
+ return jsBackend.scale(a, scalar);
3593
+ }
3594
+ return this.executeWithFallback(
3595
+ async () => {
3596
+ const aData = new Float32Array(a.toFloat64Array());
3597
+ const result = await this.backend.scale(aData, scalar);
3598
+ return DenseMatrix.fromFlat(a.rows, a.cols, Array.from(result));
3599
+ },
3600
+ () => jsBackend.scale(a, scalar)
3601
+ );
4139
3602
  }
4140
- /**
4141
- * Stream large data in chunks
4142
- */
4143
- async uploadStreaming(cpuData, gpuBuffer, onProgress) {
4144
- const id = this.nextRequestId++;
4145
- const start = performance.now();
4146
- const chunkSize = this.config.chunkSize / 4;
4147
- const totalElements = cpuData.length;
4148
- let transferred = 0;
4149
- try {
4150
- for (let offset = 0; offset < totalElements; offset += chunkSize) {
4151
- const end = Math.min(offset + chunkSize, totalElements);
4152
- const chunk = cpuData.subarray(offset, end);
4153
- this.context.writeBuffer(
4154
- gpuBuffer,
4155
- chunk,
4156
- offset * 4
4157
- // byte offset
4158
- );
4159
- transferred = end;
4160
- if (onProgress) {
4161
- onProgress(transferred / totalElements);
4162
- }
4163
- await new Promise((resolve) => setTimeout(resolve, 0));
4164
- }
4165
- await this.context.getDevice().queue.onSubmittedWorkDone();
4166
- this.totalUploads++;
4167
- this.totalBytesUploaded += totalElements * 4;
4168
- return {
4169
- id,
4170
- success: true,
4171
- duration: performance.now() - start,
4172
- bytesTransferred: totalElements * 4
4173
- };
4174
- } catch (error) {
4175
- return {
4176
- id,
4177
- success: false,
4178
- duration: performance.now() - start,
4179
- bytesTransferred: transferred * 4,
4180
- error: error instanceof Error ? error.message : String(error)
4181
- };
3603
+ abs(a) {
3604
+ return jsBackend.abs(a);
3605
+ }
3606
+ negate(a) {
3607
+ return jsBackend.negate(a);
3608
+ }
3609
+ // =========================================================================
3610
+ // Matrix Operations
3611
+ // =========================================================================
3612
+ multiply(a, b) {
3613
+ const elementCount = a.rows * b.cols * a.cols;
3614
+ if (!this.shouldUseGPU(elementCount)) {
3615
+ return jsBackend.multiply(a, b);
4182
3616
  }
3617
+ return jsBackend.multiply(a, b);
4183
3618
  }
4184
3619
  /**
4185
- * Download large data in chunks
3620
+ * Async matrix multiplication using GPU
4186
3621
  */
4187
- async downloadStreaming(gpuBuffer, totalSize, onProgress) {
4188
- const result = new Float32Array(totalSize / 4);
4189
- const chunkSize = this.config.chunkSize;
4190
- let downloaded = 0;
4191
- while (downloaded < totalSize) {
4192
- const remaining = totalSize - downloaded;
4193
- const currentChunkSize = Math.min(chunkSize, remaining);
4194
- const chunk = await this.download(gpuBuffer, {
4195
- offset: downloaded,
4196
- size: currentChunkSize
4197
- });
4198
- result.set(chunk, downloaded / 4);
4199
- downloaded += currentChunkSize;
4200
- if (onProgress) {
4201
- onProgress(downloaded / totalSize);
4202
- }
3622
+ async multiplyAsync(a, b) {
3623
+ const elementCount = a.rows * b.cols * a.cols;
3624
+ if (!this.shouldUseGPU(elementCount)) {
3625
+ return jsBackend.multiply(a, b);
4203
3626
  }
4204
- return result;
3627
+ return this.executeWithFallback(
3628
+ async () => {
3629
+ const aData = new Float32Array(a.toFloat64Array());
3630
+ const bData = new Float32Array(b.toFloat64Array());
3631
+ const result = await this.backend.matmul(aData, bData, a.rows, a.cols, b.cols);
3632
+ return DenseMatrix.fromFlat(a.rows, b.cols, Array.from(result));
3633
+ },
3634
+ () => jsBackend.multiply(a, b)
3635
+ );
3636
+ }
3637
+ transpose(a) {
3638
+ const elementCount = a.rows * a.cols;
3639
+ if (!this.shouldUseGPU(elementCount)) {
3640
+ return jsBackend.transpose(a);
3641
+ }
3642
+ return jsBackend.transpose(a);
4205
3643
  }
4206
3644
  /**
4207
- * Batch multiple transfers
3645
+ * Async transpose using GPU
4208
3646
  */
4209
- async batchTransfer(requests) {
4210
- const results = [];
4211
- const start = performance.now();
4212
- const uploads = requests.filter((r) => r.direction === "cpu-to-gpu");
4213
- const downloads = requests.filter((r) => r.direction === "gpu-to-cpu");
4214
- for (const req of uploads) {
4215
- const result = await this.upload(req.data, req.buffer);
4216
- results.push(result);
4217
- }
4218
- for (const req of downloads) {
4219
- const id = this.nextRequestId++;
4220
- try {
4221
- const data = await this.download(req.buffer);
4222
- req.data.set(data);
4223
- results.push({
4224
- id,
4225
- success: true,
4226
- duration: performance.now() - start,
4227
- bytesTransferred: data.byteLength
4228
- });
4229
- } catch (error) {
4230
- results.push({
4231
- id,
4232
- success: false,
4233
- duration: performance.now() - start,
4234
- bytesTransferred: 0,
4235
- error: error instanceof Error ? error.message : String(error)
4236
- });
4237
- }
3647
+ async transposeAsync(a) {
3648
+ const elementCount = a.rows * a.cols;
3649
+ if (!this.shouldUseGPU(elementCount)) {
3650
+ return jsBackend.transpose(a);
4238
3651
  }
4239
- return results;
3652
+ return this.executeWithFallback(
3653
+ async () => {
3654
+ const aData = new Float32Array(a.toFloat64Array());
3655
+ const result = await this.backend.transpose(aData, a.rows, a.cols);
3656
+ return DenseMatrix.fromFlat(a.cols, a.rows, Array.from(result));
3657
+ },
3658
+ () => jsBackend.transpose(a)
3659
+ );
4240
3660
  }
4241
- /**
4242
- * Create or reuse a staging buffer
4243
- */
4244
- getOrCreateStagingBuffer(size2) {
4245
- const alignedSize = this.roundToPowerOf2(size2);
4246
- for (const buffer2 of this.stagingBuffers) {
4247
- if (buffer2.size >= alignedSize) {
4248
- return buffer2;
4249
- }
4250
- }
4251
- const buffer = this.context.createStagingBuffer(alignedSize, "sync-staging");
4252
- this.stagingBuffers.push(buffer);
4253
- return buffer;
3661
+ // =========================================================================
3662
+ // Reduction Operations
3663
+ // =========================================================================
3664
+ sum(a) {
3665
+ return jsBackend.sum(a);
4254
3666
  }
4255
- /**
4256
- * Round up to next power of 2
4257
- */
4258
- roundToPowerOf2(n) {
4259
- if (n <= 0) return 256;
4260
- n--;
4261
- n |= n >> 1;
4262
- n |= n >> 2;
4263
- n |= n >> 4;
4264
- n |= n >> 8;
4265
- n |= n >> 16;
4266
- return n + 1;
3667
+ sumAxis(a, axis) {
3668
+ return jsBackend.sumAxis(a, axis);
3669
+ }
3670
+ norm(a) {
3671
+ return jsBackend.norm(a);
3672
+ }
3673
+ dot(a, b) {
3674
+ return jsBackend.dot(a, b);
4267
3675
  }
3676
+ // =========================================================================
3677
+ // Configuration
3678
+ // =========================================================================
4268
3679
  /**
4269
- * Wait for all pending transfers to complete
3680
+ * Update configuration
4270
3681
  */
4271
- async flush() {
4272
- await this.context.getDevice().queue.onSubmittedWorkDone();
4273
- await Promise.all(this.pendingTransfers.values());
4274
- this.pendingTransfers.clear();
3682
+ updateConfig(config) {
3683
+ this.config = { ...this.config, ...config };
4275
3684
  }
4276
3685
  /**
4277
- * Get synchronization statistics
3686
+ * Get current configuration
4278
3687
  */
4279
- getStats() {
4280
- return {
4281
- totalUploads: this.totalUploads,
4282
- totalDownloads: this.totalDownloads,
4283
- totalBytesUploaded: this.totalBytesUploaded,
4284
- totalBytesDownloaded: this.totalBytesDownloaded,
4285
- pendingTransfers: this.pendingTransfers.size,
4286
- stagingBuffersCount: this.stagingBuffers.length,
4287
- strategy: this.config.strategy
4288
- };
3688
+ getConfig() {
3689
+ return { ...this.config };
4289
3690
  }
4290
3691
  /**
4291
- * Destroy sync manager and release resources
3692
+ * Destroy the backend
4292
3693
  */
4293
3694
  destroy() {
4294
- for (const buffer of this.stagingBuffers) {
4295
- buffer.destroy();
3695
+ if (this.backend && !this.config.useGlobalBackend) {
3696
+ this.backend.destroy();
4296
3697
  }
4297
- this.stagingBuffers = [];
4298
- this.pendingTransfers.clear();
3698
+ this.backend = null;
4299
3699
  }
4300
3700
  };
4301
- function createSyncManager(context, bufferPool, strategy = "lazy") {
4302
- return new SyncManager(context, bufferPool, { strategy });
3701
+ var gpuMatrixBackend = new GPUMatrixBackend();
3702
+ function createGPUMatrixBackend(config) {
3703
+ return new GPUMatrixBackend(config);
4303
3704
  }
4304
3705
 
4305
- // src/backends/GPUBackend.ts
4306
- var DEFAULT_THRESHOLD = 256;
4307
- var GPUBackend = class {
4308
- context = null;
4309
- bufferPool = null;
4310
- shaderManager = null;
4311
- _status = "uninitialized";
4312
- _capabilities = null;
4313
- _lastError = null;
4314
- threshold;
4315
- workgroupSize = [16, 16, 1];
4316
- useGlobalContext;
4317
- constructor(options = {}) {
4318
- this.threshold = options.threshold ?? DEFAULT_THRESHOLD;
4319
- this.useGlobalContext = options.useGlobalContext ?? true;
3706
+ // src/config.ts
3707
+ var DEFAULT_CONFIG3 = {
3708
+ backends: {
3709
+ js: {
3710
+ enabled: true,
3711
+ preference: "auto",
3712
+ threshold: 0,
3713
+ // Always available
3714
+ operationThresholds: {}
3715
+ },
3716
+ wasm: {
3717
+ enabled: true,
3718
+ preference: "auto",
3719
+ threshold: 1e3,
3720
+ // >1000 elements
3721
+ operationThresholds: {
3722
+ multiply: 500,
3723
+ decomposition: 100,
3724
+ transpose: 2e3
3725
+ }
3726
+ },
3727
+ gpu: {
3728
+ enabled: true,
3729
+ preference: "auto",
3730
+ threshold: 1e5,
3731
+ // >100K elements
3732
+ operationThresholds: {
3733
+ multiply: 5e4,
3734
+ decomposition: 1e4,
3735
+ transpose: 2e5
3736
+ }
3737
+ }
3738
+ },
3739
+ adaptiveTuning: {
3740
+ enabled: true,
3741
+ sampleSize: 10,
3742
+ minSpeedupRatio: 1.2,
3743
+ // 20% improvement to switch
3744
+ maxAdjustmentPercent: 25,
3745
+ cooldownMs: 5e3
3746
+ },
3747
+ profiling: {
3748
+ enabled: false,
3749
+ slowOperationThresholdMs: 100,
3750
+ collectStats: false
3751
+ },
3752
+ precision: "double",
3753
+ debug: false
3754
+ };
3755
+ var currentConfig = { ...DEFAULT_CONFIG3 };
3756
+ var listeners = /* @__PURE__ */ new Set();
3757
+ function getConfig() {
3758
+ return currentConfig;
3759
+ }
3760
+ function onConfigChange(listener) {
3761
+ listeners.add(listener);
3762
+ return () => listeners.delete(listener);
3763
+ }
3764
+
3765
+ // src/backends/register-backends.ts
3766
+ backendRegistry.register(jsBackend);
3767
+ backendRegistry.register(wasmBackend);
3768
+
3769
+ // src/backends/BackendManager.ts
3770
+ var DEFAULT_EXTENDED_HINTS = {
3771
+ ...DEFAULT_BACKEND_HINTS,
3772
+ operationThresholds: {
3773
+ // matmul: the SIMD-WASM kernel wins from ~256 elems (16²), solidly 1.3–2.2× (measured,
3774
+ // tools/benchmarks/matmul-threshold.mjs); below that copy/alloc overhead dominates (8²
3775
+ // loses, 12² marginal). Dropped 500→256 to stop forcing 16²–22² matmuls onto JS.
3776
+ multiply: { wasm: 256, gpu: 5e4 },
3777
+ decomposition: { wasm: 100, gpu: 1e4 },
3778
+ // transpose WASM is retired (memory-bound, lost 4–6×) — WASMBackend.transpose always uses
3779
+ // JS regardless of this gate; kept high so the manager doesn't even select the WASM backend.
3780
+ transpose: { wasm: 2e3, gpu: 2e5 }
3781
+ },
3782
+ autoSIMD: true,
3783
+ fallbackOnError: true
3784
+ };
3785
+ var BackendManager = class {
3786
+ hints;
3787
+ initialized = false;
3788
+ initializationPromise = null;
3789
+ adaptiveState;
3790
+ configUnsubscribe = null;
3791
+ constructor(hints = {}) {
3792
+ this.hints = { ...DEFAULT_EXTENDED_HINTS, ...hints };
3793
+ this.adaptiveState = {
3794
+ samples: [],
3795
+ lastAdjustment: 0,
3796
+ adjustedThresholds: /* @__PURE__ */ new Map()
3797
+ };
3798
+ this.configUnsubscribe = onConfigChange((config) => {
3799
+ this.syncWithConfig(config);
3800
+ });
4320
3801
  }
4321
3802
  /**
4322
- * Get the current status
3803
+ * Sync manager state with global config
4323
3804
  */
4324
- get status() {
4325
- return this._status;
3805
+ syncWithConfig(config) {
3806
+ const { backends } = config;
3807
+ this.hints.wasmThreshold = backends.wasm.threshold;
3808
+ this.hints.gpuThreshold = backends.gpu.threshold;
3809
+ if (backends.wasm.operationThresholds || backends.gpu.operationThresholds) {
3810
+ const opThresholds = {};
3811
+ for (const op of ["multiply", "decomposition", "transpose"]) {
3812
+ opThresholds[op] = {
3813
+ wasm: backends.wasm.operationThresholds?.[op] ?? this.hints.wasmThreshold,
3814
+ gpu: backends.gpu.operationThresholds?.[op] ?? this.hints.gpuThreshold
3815
+ };
3816
+ }
3817
+ this.hints.operationThresholds = opThresholds;
3818
+ }
4326
3819
  }
4327
3820
  /**
4328
- * Check if backend is ready
3821
+ * Initialize all available backends
4329
3822
  */
4330
- get isReady() {
4331
- return this._status === "ready";
3823
+ async initialize() {
3824
+ if (this.initialized) return;
3825
+ if (this.initializationPromise) {
3826
+ return this.initializationPromise;
3827
+ }
3828
+ this.initializationPromise = this.doInitialize();
3829
+ return this.initializationPromise;
4332
3830
  }
4333
- /**
4334
- * Get capabilities
4335
- */
4336
- get capabilities() {
4337
- return this._capabilities;
3831
+ async doInitialize() {
3832
+ const available = backendRegistry.available();
3833
+ const initPromises = available.map(async (type) => {
3834
+ try {
3835
+ await backendRegistry.initialize(type);
3836
+ } catch (error) {
3837
+ console.warn(`Failed to initialize ${type} backend:`, error);
3838
+ }
3839
+ });
3840
+ await Promise.all(initPromises);
3841
+ this.initialized = true;
4338
3842
  }
4339
3843
  /**
4340
- * Get last error
3844
+ * Update backend hints
4341
3845
  */
4342
- get lastError() {
4343
- return this._lastError;
3846
+ setHints(hints) {
3847
+ this.hints = { ...this.hints, ...hints };
3848
+ backendRegistry.setHints(hints);
4344
3849
  }
4345
3850
  /**
4346
- * Initialize the GPU backend
3851
+ * Get current hints
4347
3852
  */
4348
- async initialize(options = {}) {
4349
- if (this._status === "ready") {
4350
- return true;
3853
+ getHints() {
3854
+ return { ...this.hints };
3855
+ }
3856
+ /**
3857
+ * Get the best backend for a given operation and matrix size.
3858
+ *
3859
+ * Selection priority:
3860
+ * 1. Preferred backend (if explicitly set)
3861
+ * 2. Elements > gpuThreshold -> GPU (if available)
3862
+ * 3. Elements > wasmThreshold -> AS WASM (if loaded)
3863
+ * 4. JS fallback
3864
+ */
3865
+ selectBackend(elementCount, operation) {
3866
+ const { preferredBackend, operationThresholds, wasmThreshold, gpuThreshold } = this.hints;
3867
+ if (preferredBackend !== "js" && backendRegistry.has(preferredBackend)) {
3868
+ const backend = backendRegistry.get(preferredBackend);
3869
+ if (backend) {
3870
+ return backend;
3871
+ }
4351
3872
  }
4352
- if (this._status === "initializing") {
4353
- throw new Error("Already initializing");
3873
+ let wasmThresh = wasmThreshold;
3874
+ let gpuThresh = gpuThreshold;
3875
+ if (operation && operationThresholds?.[operation]) {
3876
+ const opThresh = operationThresholds[operation];
3877
+ if (opThresh?.wasm !== void 0) wasmThresh = opThresh.wasm;
3878
+ if (opThresh?.gpu !== void 0) gpuThresh = opThresh.gpu;
4354
3879
  }
4355
- this._status = "initializing";
4356
- try {
4357
- if (!hasWebGPU()) {
4358
- this._status = "unsupported";
4359
- this._lastError = new Error("WebGPU is not supported");
4360
- return false;
4361
- }
4362
- this._capabilities = await detectGPUCapabilities(options.preferHighPerformance ?? true);
4363
- if (!this._capabilities.supported) {
4364
- this._status = "unsupported";
4365
- this._lastError = new Error("WebGPU adapter not available");
4366
- return false;
4367
- }
4368
- if (this.useGlobalContext) {
4369
- this.context = getGlobalGPUContext();
4370
- } else {
4371
- this.context = new GPUContext({ label: "GPUBackend" });
4372
- }
4373
- const success = await this.context.initialize(options);
4374
- if (!success) {
4375
- this._status = "error";
4376
- this._lastError = this.context.lastError;
4377
- return false;
4378
- }
4379
- this.bufferPool = new BufferPool(this.context, options.bufferPoolOptions);
4380
- this.shaderManager = new ShaderManager(this.context);
4381
- this.shaderManager.precompileBuiltins();
4382
- this.workgroupSize = getRecommendedWorkgroupSize(this._capabilities);
4383
- this._status = "ready";
4384
- return true;
4385
- } catch (error) {
4386
- this._status = "error";
4387
- this._lastError = error;
4388
- return false;
3880
+ if (elementCount >= gpuThresh && backendRegistry.has("gpu")) {
3881
+ const gpuBackend = backendRegistry.get("gpu");
3882
+ if (gpuBackend) return gpuBackend;
3883
+ }
3884
+ if (elementCount >= wasmThresh && backendRegistry.has("wasm")) {
3885
+ const wasmBackend2 = backendRegistry.get("wasm");
3886
+ if (wasmBackend2) return wasmBackend2;
4389
3887
  }
3888
+ return jsBackend;
4390
3889
  }
4391
3890
  /**
4392
- * Check if GPU should be used for the given matrix size
3891
+ * Execute an operation with automatic backend selection
4393
3892
  */
4394
- shouldUseGPU(rows, cols) {
4395
- if (!this.isReady) {
4396
- return false;
3893
+ executeWithFallback(operation, fallback) {
3894
+ if (!this.hints.fallbackOnError) {
3895
+ return operation();
3896
+ }
3897
+ try {
3898
+ return operation();
3899
+ } catch (error) {
3900
+ console.warn("Backend operation failed, falling back to JS:", error);
3901
+ return fallback();
4397
3902
  }
4398
- return rows >= this.threshold || cols >= this.threshold;
4399
3903
  }
3904
+ // =========================================================================
3905
+ // Element-wise Operations
3906
+ // =========================================================================
4400
3907
  /**
4401
- * Calculate workgroup counts for a matrix
3908
+ * Matrix addition with auto backend selection
4402
3909
  */
4403
- calculateWorkgroups(rows, cols) {
4404
- const [wgX, wgY] = this.workgroupSize;
4405
- return [Math.ceil(cols / wgX), Math.ceil(rows / wgY), 1];
3910
+ add(a, b) {
3911
+ const backend = this.selectBackend(a.length, "add");
3912
+ return this.executeWithFallback(
3913
+ () => backend.add(a, b),
3914
+ () => jsBackend.add(a, b)
3915
+ );
4406
3916
  }
4407
3917
  /**
4408
- * Get the GPU context
3918
+ * Matrix subtraction with auto backend selection
4409
3919
  */
4410
- getContext() {
4411
- if (!this.context || !this.isReady) {
4412
- throw new Error("GPUBackend not initialized");
4413
- }
4414
- return this.context;
3920
+ subtract(a, b) {
3921
+ const backend = this.selectBackend(a.length, "subtract");
3922
+ return this.executeWithFallback(
3923
+ () => backend.subtract(a, b),
3924
+ () => jsBackend.subtract(a, b)
3925
+ );
4415
3926
  }
4416
3927
  /**
4417
- * Get the buffer pool
3928
+ * Element-wise multiplication with auto backend selection
4418
3929
  */
4419
- getBufferPool() {
4420
- if (!this.bufferPool || !this.isReady) {
4421
- throw new Error("GPUBackend not initialized");
4422
- }
4423
- return this.bufferPool;
3930
+ multiplyElementwise(a, b) {
3931
+ const backend = this.selectBackend(a.length, "multiplyElementwise");
3932
+ return this.executeWithFallback(
3933
+ () => backend.multiplyElementwise(a, b),
3934
+ () => jsBackend.multiplyElementwise(a, b)
3935
+ );
4424
3936
  }
4425
3937
  /**
4426
- * Get the shader manager
3938
+ * Element-wise division with auto backend selection
4427
3939
  */
4428
- getShaderManager() {
4429
- if (!this.shaderManager || !this.isReady) {
4430
- throw new Error("GPUBackend not initialized");
4431
- }
4432
- return this.shaderManager;
3940
+ divideElementwise(a, b) {
3941
+ const backend = this.selectBackend(a.length);
3942
+ return this.executeWithFallback(
3943
+ () => backend.divideElementwise(a, b),
3944
+ () => jsBackend.divideElementwise(a, b)
3945
+ );
4433
3946
  }
4434
3947
  /**
4435
- * Add two matrices element-wise
3948
+ * Scalar multiplication with auto backend selection
4436
3949
  */
4437
- async add(a, b, rows, cols) {
4438
- const ctx = this.getContext();
4439
- const pool = this.getBufferPool();
4440
- const shaders = this.getShaderManager();
4441
- const size2 = rows * cols * 4;
4442
- const bufferA = pool.acquireStorageBuffer(size2, "matrix-a", true, true);
4443
- const bufferB = pool.acquireStorageBuffer(size2, "matrix-b", true, true);
4444
- const bufferResult = pool.acquireStorageBuffer(size2, "matrix-result", true, false);
4445
- const bufferParams = pool.acquireUniformBuffer(16, "params");
4446
- ctx.writeBuffer(bufferA, a);
4447
- ctx.writeBuffer(bufferB, b);
4448
- ctx.writeBuffer(bufferParams, new Uint32Array([rows, cols, 0, 0]));
4449
- const pipeline = shaders.getBuiltinPipeline("matrixAdd");
4450
- const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
4451
- { binding: 0, resource: { buffer: bufferA } },
4452
- { binding: 1, resource: { buffer: bufferB } },
4453
- { binding: 2, resource: { buffer: bufferResult } },
4454
- { binding: 3, resource: { buffer: bufferParams } }
4455
- ]);
4456
- ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(rows, cols));
4457
- const resultData = await ctx.readBuffer(bufferResult);
4458
- pool.release(bufferA);
4459
- pool.release(bufferB);
4460
- pool.release(bufferResult);
4461
- pool.release(bufferParams);
4462
- return new Float32Array(resultData);
3950
+ scale(a, scalar) {
3951
+ const backend = this.selectBackend(a.length, "scale");
3952
+ return this.executeWithFallback(
3953
+ () => backend.scale(a, scalar),
3954
+ () => jsBackend.scale(a, scalar)
3955
+ );
4463
3956
  }
4464
3957
  /**
4465
- * Multiply two matrices
3958
+ * Element-wise absolute value with auto backend selection
4466
3959
  */
4467
- async matmul(a, b, M, K, N) {
4468
- const ctx = this.getContext();
4469
- const pool = this.getBufferPool();
4470
- const shaders = this.getShaderManager();
4471
- const sizeA = M * K * 4;
4472
- const sizeB = K * N * 4;
4473
- const sizeResult = M * N * 4;
4474
- const bufferA = pool.acquireStorageBuffer(sizeA, "matmul-a", true, true);
4475
- const bufferB = pool.acquireStorageBuffer(sizeB, "matmul-b", true, true);
4476
- const bufferResult = pool.acquireStorageBuffer(sizeResult, "matmul-result", true, false);
4477
- const bufferParams = pool.acquireUniformBuffer(16, "params");
4478
- ctx.writeBuffer(bufferA, a);
4479
- ctx.writeBuffer(bufferB, b);
4480
- ctx.writeBuffer(bufferParams, new Uint32Array([M, N, K, 0]));
4481
- const pipeline = shaders.getBuiltinPipeline("matmul");
4482
- const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
4483
- { binding: 0, resource: { buffer: bufferA } },
4484
- { binding: 1, resource: { buffer: bufferB } },
4485
- { binding: 2, resource: { buffer: bufferResult } },
4486
- { binding: 3, resource: { buffer: bufferParams } }
4487
- ]);
4488
- ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(M, N));
4489
- const resultData = await ctx.readBuffer(bufferResult);
4490
- pool.release(bufferA);
4491
- pool.release(bufferB);
4492
- pool.release(bufferResult);
4493
- pool.release(bufferParams);
4494
- return new Float32Array(resultData);
3960
+ abs(a) {
3961
+ const backend = this.selectBackend(a.length);
3962
+ return this.executeWithFallback(
3963
+ () => backend.abs(a),
3964
+ () => jsBackend.abs(a)
3965
+ );
4495
3966
  }
4496
3967
  /**
4497
- * Transpose a matrix
3968
+ * Element-wise negation with auto backend selection
4498
3969
  */
4499
- async transpose(a, rows, cols) {
4500
- const ctx = this.getContext();
4501
- const pool = this.getBufferPool();
4502
- const shaders = this.getShaderManager();
4503
- const size2 = rows * cols * 4;
4504
- const bufferA = pool.acquireStorageBuffer(size2, "transpose-a", true, true);
4505
- const bufferResult = pool.acquireStorageBuffer(size2, "transpose-result", true, false);
4506
- const bufferParams = pool.acquireUniformBuffer(16, "params");
4507
- ctx.writeBuffer(bufferA, a);
4508
- ctx.writeBuffer(bufferParams, new Uint32Array([rows, cols, 0, 0]));
4509
- const pipeline = shaders.getBuiltinPipeline("transpose");
4510
- const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
4511
- { binding: 0, resource: { buffer: bufferA } },
4512
- { binding: 1, resource: { buffer: bufferResult } },
4513
- { binding: 2, resource: { buffer: bufferParams } }
4514
- ]);
4515
- ctx.dispatchCompute(pipeline, [bindGroup], this.calculateWorkgroups(rows, cols));
4516
- const resultData = await ctx.readBuffer(bufferResult);
4517
- pool.release(bufferA);
4518
- pool.release(bufferResult);
4519
- pool.release(bufferParams);
4520
- return new Float32Array(resultData);
3970
+ negate(a) {
3971
+ const backend = this.selectBackend(a.length);
3972
+ return this.executeWithFallback(
3973
+ () => backend.negate(a),
3974
+ () => jsBackend.negate(a)
3975
+ );
4521
3976
  }
3977
+ // =========================================================================
3978
+ // Matrix Operations
3979
+ // =========================================================================
4522
3980
  /**
4523
- * Scale a matrix by a scalar
3981
+ * Matrix multiplication with auto backend selection
4524
3982
  */
4525
- async scale(a, scalar) {
4526
- const ctx = this.getContext();
4527
- const pool = this.getBufferPool();
4528
- const shaders = this.getShaderManager();
4529
- const size2 = a.length * 4;
4530
- const bufferA = pool.acquireStorageBuffer(size2, "scale-a", true, true);
4531
- const bufferResult = pool.acquireStorageBuffer(size2, "scale-result", true, false);
4532
- const bufferParams = pool.acquireUniformBuffer(16, "params");
4533
- ctx.writeBuffer(bufferA, a);
4534
- ctx.writeBuffer(bufferParams, new Float32Array([scalar, a.length, 0, 0]));
4535
- const pipeline = shaders.getBuiltinPipeline("scalarMul");
4536
- const bindGroup = ctx.createBindGroup(pipeline.getBindGroupLayout(0), [
4537
- { binding: 0, resource: { buffer: bufferA } },
4538
- { binding: 1, resource: { buffer: bufferResult } },
4539
- { binding: 2, resource: { buffer: bufferParams } }
4540
- ]);
4541
- const workgroups = Math.ceil(a.length / 256);
4542
- ctx.dispatchCompute(pipeline, [bindGroup], [workgroups, 1, 1]);
4543
- const resultData = await ctx.readBuffer(bufferResult);
4544
- pool.release(bufferA);
4545
- pool.release(bufferResult);
4546
- pool.release(bufferParams);
4547
- return new Float32Array(resultData);
3983
+ multiply(a, b) {
3984
+ const elementCount = a.rows * b.cols * a.cols;
3985
+ const backend = this.selectBackend(elementCount, "multiply");
3986
+ return this.executeWithFallback(
3987
+ () => backend.multiply(a, b),
3988
+ () => jsBackend.multiply(a, b)
3989
+ );
4548
3990
  }
4549
3991
  /**
4550
- * Get backend statistics
3992
+ * Matrix transpose with auto backend selection
4551
3993
  */
4552
- getStats() {
4553
- return {
4554
- status: this._status,
4555
- capabilities: this._capabilities,
4556
- bufferPool: this.bufferPool?.getStats() || null,
4557
- shaders: this.shaderManager?.getStats() || null
4558
- };
3994
+ transpose(a) {
3995
+ const backend = this.selectBackend(a.length, "transpose");
3996
+ return this.executeWithFallback(
3997
+ () => backend.transpose(a),
3998
+ () => jsBackend.transpose(a)
3999
+ );
4559
4000
  }
4001
+ // =========================================================================
4002
+ // Reduction Operations
4003
+ // =========================================================================
4560
4004
  /**
4561
- * Destroy the backend
4005
+ * Sum of all elements with auto backend selection
4562
4006
  */
4563
- destroy() {
4564
- if (this.bufferPool) {
4565
- this.bufferPool.destroy();
4566
- this.bufferPool = null;
4567
- }
4568
- if (this.shaderManager) {
4569
- this.shaderManager.clearCache();
4570
- this.shaderManager = null;
4571
- }
4572
- if (this.context && !this.useGlobalContext) {
4573
- this.context.destroy();
4574
- }
4575
- this.context = null;
4576
- this._status = "uninitialized";
4577
- }
4578
- };
4579
- var globalBackend = null;
4580
- function getGlobalGPUBackend() {
4581
- if (!globalBackend) {
4582
- globalBackend = new GPUBackend({ useGlobalContext: true });
4583
- }
4584
- return globalBackend;
4585
- }
4586
- async function initializeGlobalGPUBackend(options) {
4587
- const backend = getGlobalGPUBackend();
4588
- return backend.initialize(options);
4589
- }
4590
- function destroyGlobalGPUBackend() {
4591
- if (globalBackend) {
4592
- globalBackend.destroy();
4593
- globalBackend = null;
4594
- }
4595
- }
4596
-
4597
- // src/backends/GPUMatrixBackend.ts
4598
- var DEFAULT_CONFIG2 = {
4599
- minElements: 65536,
4600
- // 256x256
4601
- useGlobalBackend: true,
4602
- gpuOptions: {},
4603
- fallbackOnError: true
4604
- };
4605
- var GPUMatrixBackend = class {
4606
- type = "gpu";
4607
- config;
4608
- backend = null;
4609
- capabilities = null;
4610
- initPromise = null;
4611
- _available = null;
4612
- constructor(config = {}) {
4613
- this.config = { ...DEFAULT_CONFIG2, ...config };
4007
+ async sum(a) {
4008
+ const backend = this.selectBackend(a.length);
4009
+ const result = backend.sum(a);
4010
+ return result instanceof Promise ? result : result;
4614
4011
  }
4615
4012
  /**
4616
- * Check if GPU is available in the current environment
4013
+ * Sum along axis with auto backend selection
4617
4014
  */
4618
- isAvailable() {
4619
- if (this._available !== null) {
4620
- return this._available;
4621
- }
4622
- this._available = hasWebGPU();
4623
- return this._available;
4015
+ sumAxis(a, axis) {
4016
+ const backend = this.selectBackend(a.length);
4017
+ return this.executeWithFallback(
4018
+ () => backend.sumAxis(a, axis),
4019
+ () => jsBackend.sumAxis(a, axis)
4020
+ );
4624
4021
  }
4625
4022
  /**
4626
- * Initialize the GPU backend
4023
+ * Frobenius norm with auto backend selection
4627
4024
  */
4628
- async initialize() {
4629
- if (this.backend?.isReady) return;
4630
- if (this.initPromise) return this.initPromise;
4631
- this.initPromise = this.doInitialize();
4632
- return this.initPromise;
4633
- }
4634
- async doInitialize() {
4635
- if (!this.isAvailable()) {
4636
- throw new Error("WebGPU is not available in this environment");
4637
- }
4638
- this.capabilities = await detectGPUCapabilities(
4639
- this.config.gpuOptions?.preferHighPerformance ?? true
4025
+ norm(a) {
4026
+ const backend = this.selectBackend(a.length);
4027
+ return this.executeWithFallback(
4028
+ () => backend.norm(a),
4029
+ () => jsBackend.norm(a)
4640
4030
  );
4641
- if (!this.capabilities.supported) {
4642
- throw new Error("WebGPU adapter not available");
4643
- }
4644
- if (this.config.useGlobalBackend) {
4645
- const success = await initializeGlobalGPUBackend(this.config.gpuOptions);
4646
- if (!success) {
4647
- throw new Error("Failed to initialize global GPU backend");
4648
- }
4649
- this.backend = getGlobalGPUBackend();
4650
- } else {
4651
- this.backend = new GPUBackend(this.config.gpuOptions);
4652
- const success = await this.backend.initialize(this.config.gpuOptions);
4653
- if (!success) {
4654
- throw new Error(`Failed to initialize GPU backend: ${this.backend.lastError?.message}`);
4655
- }
4656
- }
4657
4031
  }
4658
4032
  /**
4659
- * Check if operation should use GPU
4033
+ * Dot product with auto backend selection
4660
4034
  */
4661
- shouldUseGPU(elementCount) {
4662
- return this.backend !== null && this.backend.isReady && elementCount >= this.config.minElements;
4035
+ async dot(a, b) {
4036
+ const backend = this.selectBackend(a.length);
4037
+ const result = backend.dot(a, b);
4038
+ return result instanceof Promise ? result : result;
4663
4039
  }
4040
+ // =========================================================================
4041
+ // Backend Info
4042
+ // =========================================================================
4664
4043
  /**
4665
- * Execute GPU operation with fallback
4044
+ * Get list of available backends
4666
4045
  */
4667
- async executeWithFallback(gpuOp, fallback) {
4668
- if (!this.config.fallbackOnError) {
4669
- return gpuOp();
4670
- }
4671
- try {
4672
- return await gpuOp();
4673
- } catch (error) {
4674
- console.warn("GPU operation failed, falling back to JS:", error);
4675
- return fallback();
4676
- }
4046
+ getAvailableBackends() {
4047
+ return backendRegistry.available();
4677
4048
  }
4678
4049
  /**
4679
- * Get GPU capabilities
4050
+ * Check if a specific backend is available
4680
4051
  */
4681
- getCapabilities() {
4682
- return this.capabilities;
4052
+ hasBackend(type) {
4053
+ return backendRegistry.has(type);
4683
4054
  }
4684
4055
  /**
4685
- * Get backend statistics
4056
+ * Get current active backend for a given operation size
4686
4057
  */
4687
- getStats() {
4688
- return this.backend?.getStats() ?? null;
4058
+ getActiveBackend(elementCount, operation) {
4059
+ return this.selectBackend(elementCount, operation).type;
4689
4060
  }
4690
- // =========================================================================
4691
- // Element-wise Operations
4692
- // =========================================================================
4693
- add(a, b) {
4694
- const elementCount = a.rows * a.cols;
4695
- if (!this.shouldUseGPU(elementCount)) {
4696
- return jsBackend.add(a, b);
4061
+ /**
4062
+ * Force a specific backend for all operations
4063
+ */
4064
+ forceBackend(type) {
4065
+ if (type === null) {
4066
+ this.hints.preferredBackend = "js";
4067
+ } else {
4068
+ this.hints.preferredBackend = type;
4697
4069
  }
4698
- return jsBackend.add(a, b);
4699
4070
  }
4071
+ // =========================================================================
4072
+ // Adaptive Threshold Tuning
4073
+ // =========================================================================
4700
4074
  /**
4701
- * Async add operation using GPU
4075
+ * Record a performance sample for adaptive tuning
4702
4076
  */
4703
- async addAsync(a, b) {
4704
- const elementCount = a.rows * a.cols;
4705
- if (!this.shouldUseGPU(elementCount)) {
4706
- return jsBackend.add(a, b);
4077
+ recordSample(operation, elementCount, backend, durationMs) {
4078
+ const config = getConfig();
4079
+ if (!config.adaptiveTuning.enabled) return;
4080
+ this.adaptiveState.samples.push({
4081
+ operation,
4082
+ elementCount,
4083
+ backend,
4084
+ durationMs,
4085
+ timestamp: Date.now()
4086
+ });
4087
+ const maxSamples = config.adaptiveTuning.sampleSize * 10;
4088
+ if (this.adaptiveState.samples.length > maxSamples) {
4089
+ this.adaptiveState.samples = this.adaptiveState.samples.slice(-maxSamples);
4707
4090
  }
4708
- return this.executeWithFallback(
4709
- async () => {
4710
- const aData = new Float32Array(a.toFloat64Array());
4711
- const bData = new Float32Array(b.toFloat64Array());
4712
- const result = await this.backend.add(aData, bData, a.rows, a.cols);
4713
- return DenseMatrix.fromFlat(a.rows, a.cols, Array.from(result));
4714
- },
4715
- () => jsBackend.add(a, b)
4716
- );
4091
+ this.maybeAdjustThresholds();
4717
4092
  }
4718
- subtract(a, b) {
4719
- const elementCount = a.rows * a.cols;
4720
- if (!this.shouldUseGPU(elementCount)) {
4721
- return jsBackend.subtract(a, b);
4093
+ /**
4094
+ * Adjust thresholds based on collected samples
4095
+ */
4096
+ maybeAdjustThresholds() {
4097
+ const config = getConfig();
4098
+ if (!config.adaptiveTuning.enabled) return;
4099
+ const now = Date.now();
4100
+ const { cooldownMs, sampleSize, minSpeedupRatio, maxAdjustmentPercent } = config.adaptiveTuning;
4101
+ if (now - this.adaptiveState.lastAdjustment < cooldownMs) {
4102
+ return;
4722
4103
  }
4723
- return jsBackend.subtract(a, b);
4724
- }
4725
- multiplyElementwise(a, b) {
4726
- const elementCount = a.rows * a.cols;
4727
- if (!this.shouldUseGPU(elementCount)) {
4728
- return jsBackend.multiplyElementwise(a, b);
4729
- }
4730
- return jsBackend.multiplyElementwise(a, b);
4731
- }
4732
- divideElementwise(a, b) {
4733
- const elementCount = a.rows * a.cols;
4734
- if (!this.shouldUseGPU(elementCount)) {
4735
- return jsBackend.divideElementwise(a, b);
4104
+ const operationSamples = /* @__PURE__ */ new Map();
4105
+ for (const sample of this.adaptiveState.samples) {
4106
+ const existing = operationSamples.get(sample.operation) ?? [];
4107
+ existing.push(sample);
4108
+ operationSamples.set(sample.operation, existing);
4736
4109
  }
4737
- return jsBackend.divideElementwise(a, b);
4738
- }
4739
- scale(a, scalar) {
4740
- const elementCount = a.rows * a.cols;
4741
- if (!this.shouldUseGPU(elementCount)) {
4742
- return jsBackend.scale(a, scalar);
4110
+ for (const [operation, samples] of operationSamples) {
4111
+ if (samples.length < sampleSize) continue;
4112
+ const byBackend = /* @__PURE__ */ new Map();
4113
+ for (const s of samples) {
4114
+ const existing = byBackend.get(s.backend) ?? [];
4115
+ existing.push(s);
4116
+ byBackend.set(s.backend, existing);
4117
+ }
4118
+ const throughput = /* @__PURE__ */ new Map();
4119
+ for (const [backend, backendSamples] of byBackend) {
4120
+ const totalElements = backendSamples.reduce((s, x) => s + x.elementCount, 0);
4121
+ const totalTime = backendSamples.reduce((s, x) => s + x.durationMs, 0);
4122
+ if (totalTime > 0) {
4123
+ throughput.set(backend, totalElements / totalTime);
4124
+ }
4125
+ }
4126
+ const jsThroughput = throughput.get("js") ?? 0;
4127
+ const wasmThroughput = throughput.get("wasm") ?? 0;
4128
+ const gpuThroughput = throughput.get("gpu") ?? 0;
4129
+ const currentThresholds = this.adaptiveState.adjustedThresholds.get(operation) ?? {
4130
+ wasm: this.hints.operationThresholds?.[operation]?.wasm ?? this.hints.wasmThreshold,
4131
+ gpu: this.hints.operationThresholds?.[operation]?.gpu ?? this.hints.gpuThreshold
4132
+ };
4133
+ let newWasmThreshold = currentThresholds.wasm;
4134
+ let newGpuThreshold = currentThresholds.gpu;
4135
+ if (wasmThroughput > jsThroughput * minSpeedupRatio) {
4136
+ const adjustment = currentThresholds.wasm * (maxAdjustmentPercent / 100);
4137
+ newWasmThreshold = Math.max(100, currentThresholds.wasm - adjustment);
4138
+ } else if (jsThroughput > wasmThroughput * minSpeedupRatio) {
4139
+ const adjustment = currentThresholds.wasm * (maxAdjustmentPercent / 100);
4140
+ newWasmThreshold = currentThresholds.wasm + adjustment;
4141
+ }
4142
+ if (gpuThroughput > wasmThroughput * minSpeedupRatio) {
4143
+ const adjustment = currentThresholds.gpu * (maxAdjustmentPercent / 100);
4144
+ newGpuThreshold = Math.max(1e3, currentThresholds.gpu - adjustment);
4145
+ } else if (wasmThroughput > gpuThroughput * minSpeedupRatio) {
4146
+ const adjustment = currentThresholds.gpu * (maxAdjustmentPercent / 100);
4147
+ newGpuThreshold = currentThresholds.gpu + adjustment;
4148
+ }
4149
+ this.adaptiveState.adjustedThresholds.set(operation, {
4150
+ wasm: Math.round(newWasmThreshold),
4151
+ gpu: Math.round(newGpuThreshold)
4152
+ });
4743
4153
  }
4744
- return jsBackend.scale(a, scalar);
4154
+ this.adaptiveState.lastAdjustment = now;
4745
4155
  }
4746
4156
  /**
4747
- * Async scale operation using GPU
4157
+ * Get current adaptive thresholds
4748
4158
  */
4749
- async scaleAsync(a, scalar) {
4750
- const elementCount = a.rows * a.cols;
4751
- if (!this.shouldUseGPU(elementCount)) {
4752
- return jsBackend.scale(a, scalar);
4753
- }
4754
- return this.executeWithFallback(
4755
- async () => {
4756
- const aData = new Float32Array(a.toFloat64Array());
4757
- const result = await this.backend.scale(aData, scalar);
4758
- return DenseMatrix.fromFlat(a.rows, a.cols, Array.from(result));
4759
- },
4760
- () => jsBackend.scale(a, scalar)
4761
- );
4762
- }
4763
- abs(a) {
4764
- return jsBackend.abs(a);
4765
- }
4766
- negate(a) {
4767
- return jsBackend.negate(a);
4159
+ getAdaptiveThresholds() {
4160
+ return new Map(this.adaptiveState.adjustedThresholds);
4768
4161
  }
4769
- // =========================================================================
4770
- // Matrix Operations
4771
- // =========================================================================
4772
- multiply(a, b) {
4773
- const elementCount = a.rows * b.cols * a.cols;
4774
- if (!this.shouldUseGPU(elementCount)) {
4775
- return jsBackend.multiply(a, b);
4776
- }
4777
- return jsBackend.multiply(a, b);
4162
+ /**
4163
+ * Reset adaptive tuning state
4164
+ */
4165
+ resetAdaptiveState() {
4166
+ this.adaptiveState = {
4167
+ samples: [],
4168
+ lastAdjustment: 0,
4169
+ adjustedThresholds: /* @__PURE__ */ new Map()
4170
+ };
4778
4171
  }
4779
4172
  /**
4780
- * Async matrix multiplication using GPU
4173
+ * Get performance statistics
4781
4174
  */
4782
- async multiplyAsync(a, b) {
4783
- const elementCount = a.rows * b.cols * a.cols;
4784
- if (!this.shouldUseGPU(elementCount)) {
4785
- return jsBackend.multiply(a, b);
4175
+ getPerformanceStats() {
4176
+ const operationStats = /* @__PURE__ */ new Map();
4177
+ const byOperation = /* @__PURE__ */ new Map();
4178
+ for (const sample of this.adaptiveState.samples) {
4179
+ const existing = byOperation.get(sample.operation) ?? [];
4180
+ existing.push(sample);
4181
+ byOperation.set(sample.operation, existing);
4786
4182
  }
4787
- return this.executeWithFallback(
4788
- async () => {
4789
- const aData = new Float32Array(a.toFloat64Array());
4790
- const bData = new Float32Array(b.toFloat64Array());
4791
- const result = await this.backend.matmul(aData, bData, a.rows, a.cols, b.cols);
4792
- return DenseMatrix.fromFlat(a.rows, b.cols, Array.from(result));
4793
- },
4794
- () => jsBackend.multiply(a, b)
4795
- );
4796
- }
4797
- transpose(a) {
4798
- const elementCount = a.rows * a.cols;
4799
- if (!this.shouldUseGPU(elementCount)) {
4800
- return jsBackend.transpose(a);
4183
+ for (const [op, samples] of byOperation) {
4184
+ const avgDuration = samples.reduce((s, x) => s + x.durationMs, 0) / samples.length;
4185
+ const backendUsage = {
4186
+ js: 0,
4187
+ wasm: 0,
4188
+ gpu: 0,
4189
+ parallel: 0
4190
+ };
4191
+ for (const s of samples) {
4192
+ backendUsage[s.backend]++;
4193
+ }
4194
+ operationStats.set(op, {
4195
+ avgDuration,
4196
+ samples: samples.length,
4197
+ backendUsage
4198
+ });
4801
4199
  }
4802
- return jsBackend.transpose(a);
4200
+ return {
4201
+ sampleCount: this.adaptiveState.samples.length,
4202
+ operationStats
4203
+ };
4803
4204
  }
4804
4205
  /**
4805
- * Async transpose using GPU
4206
+ * Cleanup resources
4806
4207
  */
4807
- async transposeAsync(a) {
4808
- const elementCount = a.rows * a.cols;
4809
- if (!this.shouldUseGPU(elementCount)) {
4810
- return jsBackend.transpose(a);
4208
+ destroy() {
4209
+ if (this.configUnsubscribe) {
4210
+ this.configUnsubscribe();
4211
+ this.configUnsubscribe = null;
4811
4212
  }
4812
- return this.executeWithFallback(
4813
- async () => {
4814
- const aData = new Float32Array(a.toFloat64Array());
4815
- const result = await this.backend.transpose(aData, a.rows, a.cols);
4816
- return DenseMatrix.fromFlat(a.cols, a.rows, Array.from(result));
4817
- },
4818
- () => jsBackend.transpose(a)
4819
- );
4820
- }
4821
- // =========================================================================
4822
- // Reduction Operations
4823
- // =========================================================================
4824
- sum(a) {
4825
- return jsBackend.sum(a);
4826
- }
4827
- sumAxis(a, axis) {
4828
- return jsBackend.sumAxis(a, axis);
4829
4213
  }
4830
- norm(a) {
4831
- return jsBackend.norm(a);
4832
- }
4833
- dot(a, b) {
4834
- return jsBackend.dot(a, b);
4214
+ };
4215
+ var backendManager = new BackendManager();
4216
+ function createBackendManager(hints) {
4217
+ return new BackendManager(hints);
4218
+ }
4219
+
4220
+ // src/backends/WasmLoader.ts
4221
+ var AS_ID_ARRAY_BUFFER2 = 1;
4222
+ var AS_ID_INT32_ARRAY2 = 4;
4223
+ var AS_ID_FLOAT64_ARRAY2 = 5;
4224
+ var AS_HEADER_BYTES2 = 12;
4225
+ var WasmLoader = class _WasmLoader {
4226
+ static instance = null;
4227
+ wasmModule = null;
4228
+ compiledModule = null;
4229
+ loading = null;
4230
+ isNode;
4231
+ lastMetrics = null;
4232
+ // Memory pool for AS allocations.
4233
+ float64Pool = [];
4234
+ int32Pool = [];
4235
+ poolSizeThreshold = 1024 * 1024;
4236
+ // 1MB max per pool entry
4237
+ constructor() {
4238
+ this.isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
4835
4239
  }
4836
- // =========================================================================
4837
- // Configuration
4838
- // =========================================================================
4839
- /**
4840
- * Update configuration
4841
- */
4842
- updateConfig(config) {
4843
- this.config = { ...this.config, ...config };
4240
+ static getInstance() {
4241
+ if (!_WasmLoader.instance) {
4242
+ _WasmLoader.instance = new _WasmLoader();
4243
+ }
4244
+ return _WasmLoader.instance;
4844
4245
  }
4845
4246
  /**
4846
- * Get current configuration
4247
+ * Load the WASM module
4847
4248
  */
4848
- getConfig() {
4849
- return { ...this.config };
4249
+ async load(wasmPath) {
4250
+ if (this.wasmModule) {
4251
+ return this.wasmModule;
4252
+ }
4253
+ if (this.loading) {
4254
+ return this.loading;
4255
+ }
4256
+ this.loading = this.loadModule(wasmPath);
4257
+ this.wasmModule = await this.loading;
4258
+ return this.wasmModule;
4850
4259
  }
4851
4260
  /**
4852
- * Destroy the backend
4261
+ * Precompile the WASM module without instantiation
4262
+ * Useful for build-time or startup optimization
4853
4263
  */
4854
- destroy() {
4855
- if (this.backend && !this.config.useGlobalBackend) {
4856
- this.backend.destroy();
4857
- }
4858
- this.backend = null;
4859
- }
4860
- };
4861
- var gpuMatrixBackend = new GPUMatrixBackend();
4862
- function createGPUMatrixBackend(config) {
4863
- return new GPUMatrixBackend(config);
4864
- }
4865
-
4866
- // src/config.ts
4867
- var DEFAULT_CONFIG3 = {
4868
- backends: {
4869
- js: {
4870
- enabled: true,
4871
- preference: "auto",
4872
- threshold: 0,
4873
- // Always available
4874
- operationThresholds: {}
4875
- },
4876
- wasm: {
4877
- enabled: true,
4878
- preference: "auto",
4879
- threshold: 1e3,
4880
- // >1000 elements
4881
- operationThresholds: {
4882
- multiply: 500,
4883
- decomposition: 100,
4884
- transpose: 2e3
4885
- }
4886
- },
4887
- gpu: {
4888
- enabled: true,
4889
- preference: "auto",
4890
- threshold: 1e5,
4891
- // >100K elements
4892
- operationThresholds: {
4893
- multiply: 5e4,
4894
- decomposition: 1e4,
4895
- transpose: 2e5
4264
+ async precompile(wasmPath) {
4265
+ if (this.compiledModule) return;
4266
+ const path = wasmPath || await this.getDefaultWasmPath();
4267
+ const startTime = performance.now();
4268
+ const { loadWasmManifest, verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
4269
+ if (this.isNode) {
4270
+ const fs = await import("fs");
4271
+ const { promisify } = await import("util");
4272
+ const readFile = promisify(fs.readFile);
4273
+ const buffer = await readFile(path);
4274
+ await verifyWasmIntegrity(buffer, path);
4275
+ this.compiledModule = await WebAssembly.compile(buffer);
4276
+ } else {
4277
+ const manifest = await loadWasmManifest(path);
4278
+ if (!manifest && typeof WebAssembly.compileStreaming === "function") {
4279
+ this.compiledModule = await WebAssembly.compileStreaming(fetch(path));
4280
+ } else {
4281
+ const response = await fetch(path);
4282
+ const buffer = await response.arrayBuffer();
4283
+ await verifyWasmIntegrity(buffer, path, { manifest });
4284
+ this.compiledModule = await WebAssembly.compile(buffer);
4896
4285
  }
4897
4286
  }
4898
- },
4899
- adaptiveTuning: {
4900
- enabled: true,
4901
- sampleSize: 10,
4902
- minSpeedupRatio: 1.2,
4903
- // 20% improvement to switch
4904
- maxAdjustmentPercent: 25,
4905
- cooldownMs: 5e3
4906
- },
4907
- profiling: {
4908
- enabled: false,
4909
- slowOperationThresholdMs: 100,
4910
- collectStats: false
4911
- },
4912
- precision: "double",
4913
- debug: false
4914
- };
4915
- var currentConfig = { ...DEFAULT_CONFIG3 };
4916
- var listeners = /* @__PURE__ */ new Set();
4917
- function getConfig() {
4918
- return currentConfig;
4919
- }
4920
- function onConfigChange(listener) {
4921
- listeners.add(listener);
4922
- return () => listeners.delete(listener);
4923
- }
4924
-
4925
- // src/backends/register-backends.ts
4926
- backendRegistry.register(jsBackend);
4927
- backendRegistry.register(wasmBackend);
4928
-
4929
- // src/backends/BackendManager.ts
4930
- var DEFAULT_EXTENDED_HINTS = {
4931
- ...DEFAULT_BACKEND_HINTS,
4932
- operationThresholds: {
4933
- // matmul: the SIMD-WASM kernel wins from ~256 elems (16²), solidly 1.3–2.2× (measured,
4934
- // tools/benchmarks/matmul-threshold.mjs); below that copy/alloc overhead dominates (8²
4935
- // loses, 12² marginal). Dropped 500→256 to stop forcing 16²–22² matmuls onto JS.
4936
- multiply: { wasm: 256, gpu: 5e4 },
4937
- decomposition: { wasm: 100, gpu: 1e4 },
4938
- // transpose WASM is retired (memory-bound, lost 4–6×) — WASMBackend.transpose always uses
4939
- // JS regardless of this gate; kept high so the manager doesn't even select the WASM backend.
4940
- transpose: { wasm: 2e3, gpu: 2e5 }
4941
- },
4942
- autoSIMD: true,
4943
- fallbackOnError: true
4944
- };
4945
- var BackendManager = class {
4946
- hints;
4947
- initialized = false;
4948
- initializationPromise = null;
4949
- adaptiveState;
4950
- configUnsubscribe = null;
4951
- constructor(hints = {}) {
4952
- this.hints = { ...DEFAULT_EXTENDED_HINTS, ...hints };
4953
- this.adaptiveState = {
4954
- samples: [],
4955
- lastAdjustment: 0,
4956
- adjustedThresholds: /* @__PURE__ */ new Map()
4287
+ this.lastMetrics = {
4288
+ fileReadMs: 0,
4289
+ compileMs: performance.now() - startTime,
4290
+ instantiateMs: 0,
4291
+ totalMs: performance.now() - startTime,
4292
+ fromCache: false
4957
4293
  };
4958
- this.configUnsubscribe = onConfigChange((config) => {
4959
- this.syncWithConfig(config);
4960
- });
4961
4294
  }
4962
- /**
4963
- * Sync manager state with global config
4964
- */
4965
- syncWithConfig(config) {
4966
- const { backends } = config;
4967
- this.hints.wasmThreshold = backends.wasm.threshold;
4968
- this.hints.gpuThreshold = backends.gpu.threshold;
4969
- if (backends.wasm.operationThresholds || backends.gpu.operationThresholds) {
4970
- const opThresholds = {};
4971
- for (const op of ["multiply", "decomposition", "transpose"]) {
4972
- opThresholds[op] = {
4973
- wasm: backends.wasm.operationThresholds?.[op] ?? this.hints.wasmThreshold,
4974
- gpu: backends.gpu.operationThresholds?.[op] ?? this.hints.gpuThreshold
4975
- };
4976
- }
4977
- this.hints.operationThresholds = opThresholds;
4295
+ async loadModule(wasmPath) {
4296
+ const path = wasmPath || await this.getDefaultWasmPath();
4297
+ const totalStart = performance.now();
4298
+ if (this.compiledModule) {
4299
+ const instStart = performance.now();
4300
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
4301
+ this.lastMetrics = {
4302
+ fileReadMs: 0,
4303
+ compileMs: 0,
4304
+ instantiateMs: performance.now() - instStart,
4305
+ totalMs: performance.now() - totalStart,
4306
+ fromCache: true
4307
+ };
4308
+ return instance.exports;
4978
4309
  }
4979
- }
4980
- /**
4981
- * Initialize all available backends
4982
- */
4983
- async initialize() {
4984
- if (this.initialized) return;
4985
- if (this.initializationPromise) {
4986
- return this.initializationPromise;
4310
+ if (this.isNode) {
4311
+ return this.loadNodeWasm(path, totalStart);
4312
+ } else {
4313
+ return this.loadBrowserWasm(path, totalStart);
4987
4314
  }
4988
- this.initializationPromise = this.doInitialize();
4989
- return this.initializationPromise;
4990
- }
4991
- async doInitialize() {
4992
- const available = backendRegistry.available();
4993
- const initPromises = available.map(async (type) => {
4994
- try {
4995
- await backendRegistry.initialize(type);
4996
- } catch (error) {
4997
- console.warn(`Failed to initialize ${type} backend:`, error);
4998
- }
4999
- });
5000
- await Promise.all(initPromises);
5001
- this.initialized = true;
5002
- }
5003
- /**
5004
- * Update backend hints
5005
- */
5006
- setHints(hints) {
5007
- this.hints = { ...this.hints, ...hints };
5008
- backendRegistry.setHints(hints);
5009
- }
5010
- /**
5011
- * Get current hints
5012
- */
5013
- getHints() {
5014
- return { ...this.hints };
5015
4315
  }
5016
4316
  /**
5017
- * Get the best backend for a given operation and matrix size.
4317
+ * Get the WASM binary path. AssemblyScript is the sole WASM backend
4318
+ * (`mathts-as.wasm`); the legacy second toolchain was removed in the
4319
+ * WASM-backend migration (complete 2026-06-26).
5018
4320
  *
5019
- * Selection priority:
5020
- * 1. Preferred backend (if explicitly set)
5021
- * 2. Elements > gpuThreshold -> GPU (if available)
5022
- * 3. Elements > wasmThreshold -> AS WASM (if loaded)
5023
- * 4. JS fallback
4321
+ * Resolution is package-relative and CWD-independent: the packaged artifact
4322
+ * (`dist/wasm/`) is preferred via `resolvePackagedWasm`; when absent, the
4323
+ * canonical expected location is returned so the missing-binary warning is
4324
+ * actionable (see `defaultWasmLocation` in wasm/resolve.ts B-3).
5024
4325
  */
5025
- selectBackend(elementCount, operation) {
5026
- const { preferredBackend, operationThresholds, wasmThreshold, gpuThreshold } = this.hints;
5027
- if (preferredBackend !== "js" && backendRegistry.has(preferredBackend)) {
5028
- const backend = backendRegistry.get(preferredBackend);
5029
- if (backend) {
5030
- return backend;
5031
- }
5032
- }
5033
- let wasmThresh = wasmThreshold;
5034
- let gpuThresh = gpuThreshold;
5035
- if (operation && operationThresholds?.[operation]) {
5036
- const opThresh = operationThresholds[operation];
5037
- if (opThresh?.wasm !== void 0) wasmThresh = opThresh.wasm;
5038
- if (opThresh?.gpu !== void 0) gpuThresh = opThresh.gpu;
5039
- }
5040
- if (elementCount >= gpuThresh && backendRegistry.has("gpu")) {
5041
- const gpuBackend = backendRegistry.get("gpu");
5042
- if (gpuBackend) return gpuBackend;
5043
- }
5044
- if (elementCount >= wasmThresh && backendRegistry.has("wasm")) {
5045
- const wasmBackend2 = backendRegistry.get("wasm");
5046
- if (wasmBackend2) return wasmBackend2;
4326
+ async getDefaultWasmPath() {
4327
+ const wasmFile = "mathts-as.wasm";
4328
+ if (this.isNode) {
4329
+ const { resolvePackagedWasm } = await import("./resolve-VJX3YTS6.js");
4330
+ const found = await resolvePackagedWasm(import.meta.url, wasmFile);
4331
+ if (found) return found;
4332
+ const { defaultWasmLocation: defaultWasmLocation2 } = await import("./resolve-VJX3YTS6.js");
4333
+ return defaultWasmLocation2(import.meta.url, wasmFile);
5047
4334
  }
5048
- return jsBackend;
4335
+ const { defaultWasmLocation } = await import("./resolve-VJX3YTS6.js");
4336
+ return defaultWasmLocation(import.meta.url, wasmFile, { browser: true });
5049
4337
  }
5050
- /**
5051
- * Execute an operation with automatic backend selection
5052
- */
5053
- executeWithFallback(operation, fallback) {
5054
- if (!this.hints.fallbackOnError) {
5055
- return operation();
5056
- }
5057
- try {
5058
- return operation();
5059
- } catch (error) {
5060
- console.warn("Backend operation failed, falling back to JS:", error);
5061
- return fallback();
4338
+ async loadNodeWasm(path, totalStart) {
4339
+ const fs = await import("fs");
4340
+ const { promisify } = await import("util");
4341
+ const readFile = promisify(fs.readFile);
4342
+ const { verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
4343
+ const readStart = performance.now();
4344
+ const buffer = await readFile(path);
4345
+ const readEnd = performance.now();
4346
+ await verifyWasmIntegrity(buffer, path);
4347
+ const compileStart = performance.now();
4348
+ this.compiledModule = await WebAssembly.compile(buffer);
4349
+ const compileEnd = performance.now();
4350
+ const instStart = performance.now();
4351
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
4352
+ const instEnd = performance.now();
4353
+ this.lastMetrics = {
4354
+ fileReadMs: readEnd - readStart,
4355
+ compileMs: compileEnd - compileStart,
4356
+ instantiateMs: instEnd - instStart,
4357
+ totalMs: performance.now() - totalStart,
4358
+ fromCache: false
4359
+ };
4360
+ return instance.exports;
4361
+ }
4362
+ async loadBrowserWasm(path, totalStart) {
4363
+ const { loadWasmManifest, verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
4364
+ const manifest = await loadWasmManifest(path);
4365
+ if (!manifest && typeof WebAssembly.instantiateStreaming === "function") {
4366
+ const instStart2 = performance.now();
4367
+ const result = await WebAssembly.instantiateStreaming(fetch(path), this.getImports());
4368
+ this.compiledModule = result.module;
4369
+ this.lastMetrics = {
4370
+ fileReadMs: 0,
4371
+ // Combined with compile in streaming
4372
+ compileMs: 0,
4373
+ // Combined in streaming
4374
+ instantiateMs: performance.now() - instStart2,
4375
+ totalMs: performance.now() - totalStart,
4376
+ fromCache: false
4377
+ };
4378
+ return result.instance.exports;
5062
4379
  }
4380
+ const readStart = performance.now();
4381
+ const response = await fetch(path);
4382
+ const buffer = await response.arrayBuffer();
4383
+ const readEnd = performance.now();
4384
+ await verifyWasmIntegrity(buffer, path, { manifest });
4385
+ const compileStart = performance.now();
4386
+ this.compiledModule = await WebAssembly.compile(buffer);
4387
+ const compileEnd = performance.now();
4388
+ const instStart = performance.now();
4389
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
4390
+ const instEnd = performance.now();
4391
+ this.lastMetrics = {
4392
+ fileReadMs: readEnd - readStart,
4393
+ compileMs: compileEnd - compileStart,
4394
+ instantiateMs: instEnd - instStart,
4395
+ totalMs: performance.now() - totalStart,
4396
+ fromCache: false
4397
+ };
4398
+ return instance.exports;
5063
4399
  }
5064
- // =========================================================================
5065
- // Element-wise Operations
5066
- // =========================================================================
5067
- /**
5068
- * Matrix addition with auto backend selection
5069
- */
5070
- add(a, b) {
5071
- const backend = this.selectBackend(a.length, "add");
5072
- return this.executeWithFallback(
5073
- () => backend.add(a, b),
5074
- () => jsBackend.add(a, b)
5075
- );
4400
+ getImports() {
4401
+ return {
4402
+ env: {
4403
+ abort: (msg, file, line, column2) => {
4404
+ console.error("WASM abort", { msg, file, line, column: column2 });
4405
+ throw new Error("WASM abort");
4406
+ },
4407
+ seed: () => Date.now()
4408
+ },
4409
+ Math,
4410
+ Date
4411
+ };
5076
4412
  }
5077
4413
  /**
5078
- * Matrix subtraction with auto backend selection
4414
+ * Get the loaded WASM module
5079
4415
  */
5080
- subtract(a, b) {
5081
- const backend = this.selectBackend(a.length, "subtract");
5082
- return this.executeWithFallback(
5083
- () => backend.subtract(a, b),
5084
- () => jsBackend.subtract(a, b)
5085
- );
4416
+ getModule() {
4417
+ return this.wasmModule;
5086
4418
  }
5087
4419
  /**
5088
- * Element-wise multiplication with auto backend selection
4420
+ * Get the compiled WASM module (for caching/serialization)
5089
4421
  */
5090
- multiplyElementwise(a, b) {
5091
- const backend = this.selectBackend(a.length, "multiplyElementwise");
5092
- return this.executeWithFallback(
5093
- () => backend.multiplyElementwise(a, b),
5094
- () => jsBackend.multiplyElementwise(a, b)
5095
- );
4422
+ getCompiledModule() {
4423
+ return this.compiledModule;
5096
4424
  }
5097
4425
  /**
5098
- * Element-wise division with auto backend selection
4426
+ * Check if WASM is loaded
5099
4427
  */
5100
- divideElementwise(a, b) {
5101
- const backend = this.selectBackend(a.length);
5102
- return this.executeWithFallback(
5103
- () => backend.divideElementwise(a, b),
5104
- () => jsBackend.divideElementwise(a, b)
5105
- );
4428
+ isLoaded() {
4429
+ return this.wasmModule !== null;
5106
4430
  }
5107
4431
  /**
5108
- * Scalar multiplication with auto backend selection
4432
+ * Check if WASM is precompiled
5109
4433
  */
5110
- scale(a, scalar) {
5111
- const backend = this.selectBackend(a.length, "scale");
5112
- return this.executeWithFallback(
5113
- () => backend.scale(a, scalar),
5114
- () => jsBackend.scale(a, scalar)
5115
- );
4434
+ isPrecompiled() {
4435
+ return this.compiledModule !== null;
5116
4436
  }
5117
4437
  /**
5118
- * Element-wise absolute value with auto backend selection
4438
+ * Get loading performance metrics
5119
4439
  */
5120
- abs(a) {
5121
- const backend = this.selectBackend(a.length);
5122
- return this.executeWithFallback(
5123
- () => backend.abs(a),
5124
- () => jsBackend.abs(a)
5125
- );
4440
+ getLoadingMetrics() {
4441
+ return this.lastMetrics;
5126
4442
  }
4443
+ // ===========================================================================
4444
+ // Allocation API
4445
+ // ---------------------------------------------------------------------------
4446
+ // The returned handle's `ptr` is the header pointer to pass to WASM
4447
+ // functions (AssemblyScript managed runtime).
4448
+ // ===========================================================================
5127
4449
  /**
5128
- * Element-wise negation with auto backend selection
4450
+ * Allocate Float64Array in WASM memory and copy `data` into it.
4451
+ * Uses memory pooling.
5129
4452
  */
5130
- negate(a) {
5131
- const backend = this.selectBackend(a.length);
5132
- return this.executeWithFallback(
5133
- () => backend.negate(a),
5134
- () => jsBackend.negate(a)
5135
- );
4453
+ allocateFloat64Array(data) {
4454
+ const module = this.wasmModule;
4455
+ if (!module) throw new Error("WASM module not loaded");
4456
+ const length = data.length;
4457
+ const alloc = this.allocateAsFloat64(module, length);
4458
+ alloc.array.set(data);
4459
+ return alloc;
5136
4460
  }
5137
- // =========================================================================
5138
- // Matrix Operations
5139
- // =========================================================================
5140
4461
  /**
5141
- * Matrix multiplication with auto backend selection
4462
+ * Decode a managed Float64Array RETURNED by an AssemblyScript export
4463
+ * (e.g. the `matrix_lu_decompose` / `matrix_qr_decompose` outputs) into a fresh JS copy.
4464
+ *
4465
+ * AS exports that return a `Float64Array` hand back the typed-array
4466
+ * *header* pointer. The header is a 12-byte block laid out as
4467
+ * `[buffer, dataStart, byteLength]` (little-endian u32s) — the same shape
4468
+ * the loader writes in `makeAsFloat64`. We read `dataStart`/`byteLength`
4469
+ * and copy the data region out so it survives any later reuse of WASM
4470
+ * memory.
5142
4471
  */
5143
- multiply(a, b) {
5144
- const elementCount = a.rows * b.cols * a.cols;
5145
- const backend = this.selectBackend(elementCount, "multiply");
5146
- return this.executeWithFallback(
5147
- () => backend.multiply(a, b),
5148
- () => jsBackend.multiply(a, b)
5149
- );
4472
+ readReturnedFloat64Array(headerPtr) {
4473
+ const module = this.wasmModule;
4474
+ if (!module) throw new Error("WASM module not loaded");
4475
+ const hdr = headerPtr >>> 0;
4476
+ const dv = new DataView(module.memory.buffer);
4477
+ const dataStart = dv.getUint32(hdr + 4, true);
4478
+ const byteLength = dv.getUint32(hdr + 8, true);
4479
+ const view = new Float64Array(module.memory.buffer, dataStart, byteLength >>> 3);
4480
+ return new Float64Array(view);
5150
4481
  }
5151
4482
  /**
5152
- * Matrix transpose with auto backend selection
4483
+ * Allocate Float64Array without copying data (for output buffers)
5153
4484
  */
5154
- transpose(a) {
5155
- const backend = this.selectBackend(a.length, "transpose");
5156
- return this.executeWithFallback(
5157
- () => backend.transpose(a),
5158
- () => jsBackend.transpose(a)
5159
- );
4485
+ allocateFloat64ArrayEmpty(length) {
4486
+ const module = this.wasmModule;
4487
+ if (!module) throw new Error("WASM module not loaded");
4488
+ return this.allocateAsFloat64(module, length);
5160
4489
  }
5161
- // =========================================================================
5162
- // Reduction Operations
5163
- // =========================================================================
5164
4490
  /**
5165
- * Sum of all elements with auto backend selection
4491
+ * Allocate Int32Array in WASM memory and copy `data` into it.
5166
4492
  */
5167
- async sum(a) {
5168
- const backend = this.selectBackend(a.length);
5169
- const result = backend.sum(a);
5170
- return result instanceof Promise ? result : result;
4493
+ allocateInt32Array(data) {
4494
+ const module = this.wasmModule;
4495
+ if (!module) throw new Error("WASM module not loaded");
4496
+ const length = data.length;
4497
+ const alloc = this.allocateAsInt32(module, length);
4498
+ alloc.array.set(data);
4499
+ return alloc;
5171
4500
  }
5172
4501
  /**
5173
- * Sum along axis with auto backend selection
4502
+ * Allocate Int32Array without copying data (for output buffers)
5174
4503
  */
5175
- sumAxis(a, axis) {
5176
- const backend = this.selectBackend(a.length);
5177
- return this.executeWithFallback(
5178
- () => backend.sumAxis(a, axis),
5179
- () => jsBackend.sumAxis(a, axis)
5180
- );
4504
+ allocateInt32ArrayEmpty(length) {
4505
+ const module = this.wasmModule;
4506
+ if (!module) throw new Error("WASM module not loaded");
4507
+ return this.allocateAsInt32(module, length);
5181
4508
  }
5182
- /**
5183
- * Frobenius norm with auto backend selection
5184
- */
5185
- norm(a) {
5186
- const backend = this.selectBackend(a.length);
5187
- return this.executeWithFallback(
5188
- () => backend.norm(a),
5189
- () => jsBackend.norm(a)
5190
- );
4509
+ // ---- AS managed runtime (header pointers) ------------------------------
4510
+ allocateAsFloat64(module, length) {
4511
+ const byteLength = length * 8;
4512
+ if (byteLength <= this.poolSizeThreshold) {
4513
+ const recycled = this.acquireFromAsPool(this.float64Pool, byteLength);
4514
+ if (recycled) {
4515
+ const array = new Float64Array(module.memory.buffer, recycled.dataPtr, length);
4516
+ return {
4517
+ ptr: recycled.ptr,
4518
+ dataPtr: recycled.dataPtr,
4519
+ array,
4520
+ length
4521
+ };
4522
+ }
4523
+ }
4524
+ return this.makeAsFloat64(module, length);
4525
+ }
4526
+ allocateAsInt32(module, length) {
4527
+ const byteLength = length * 4;
4528
+ if (byteLength <= this.poolSizeThreshold) {
4529
+ const recycled = this.acquireFromAsPool(this.int32Pool, byteLength);
4530
+ if (recycled) {
4531
+ const array = new Int32Array(module.memory.buffer, recycled.dataPtr, length);
4532
+ return {
4533
+ ptr: recycled.ptr,
4534
+ dataPtr: recycled.dataPtr,
4535
+ array,
4536
+ length
4537
+ };
4538
+ }
4539
+ }
4540
+ return this.makeAsInt32(module, length);
4541
+ }
4542
+ makeAsFloat64(module, length) {
4543
+ const byteLength = length * 8;
4544
+ const bufferPtr = module.__pin(module.__new(byteLength, AS_ID_ARRAY_BUFFER2)) >>> 0;
4545
+ const headerPtr = module.__new(AS_HEADER_BYTES2, AS_ID_FLOAT64_ARRAY2) >>> 0;
4546
+ module.__pin(headerPtr);
4547
+ const dv = new DataView(module.memory.buffer);
4548
+ dv.setUint32(headerPtr + 0, bufferPtr, true);
4549
+ dv.setUint32(headerPtr + 4, bufferPtr, true);
4550
+ dv.setUint32(headerPtr + 8, byteLength, true);
4551
+ const array = new Float64Array(module.memory.buffer, bufferPtr, length);
4552
+ return { ptr: headerPtr, dataPtr: bufferPtr, array, length };
4553
+ }
4554
+ makeAsInt32(module, length) {
4555
+ const byteLength = length * 4;
4556
+ const bufferPtr = module.__pin(module.__new(byteLength, AS_ID_ARRAY_BUFFER2)) >>> 0;
4557
+ const headerPtr = module.__new(AS_HEADER_BYTES2, AS_ID_INT32_ARRAY2) >>> 0;
4558
+ module.__pin(headerPtr);
4559
+ const dv = new DataView(module.memory.buffer);
4560
+ dv.setUint32(headerPtr + 0, bufferPtr, true);
4561
+ dv.setUint32(headerPtr + 4, bufferPtr, true);
4562
+ dv.setUint32(headerPtr + 8, byteLength, true);
4563
+ const array = new Int32Array(module.memory.buffer, bufferPtr, length);
4564
+ return { ptr: headerPtr, dataPtr: bufferPtr, array, length };
5191
4565
  }
5192
4566
  /**
5193
- * Dot product with auto backend selection
4567
+ * Find a pool entry whose data region is large enough; returns null when
4568
+ * no suitable entry is available.
5194
4569
  */
5195
- async dot(a, b) {
5196
- const backend = this.selectBackend(a.length);
5197
- const result = backend.dot(a, b);
5198
- return result instanceof Promise ? result : result;
4570
+ acquireFromAsPool(pool, requestedSize) {
4571
+ let bestFit = null;
4572
+ let bestFitWaste = Infinity;
4573
+ for (const entry of pool) {
4574
+ if (!entry.inUse && entry.size >= requestedSize) {
4575
+ const waste = entry.size - requestedSize;
4576
+ if (waste < bestFitWaste && entry.size <= requestedSize * 2) {
4577
+ bestFit = entry;
4578
+ bestFitWaste = waste;
4579
+ }
4580
+ }
4581
+ }
4582
+ if (bestFit) bestFit.inUse = true;
4583
+ return bestFit;
5199
4584
  }
5200
- // =========================================================================
5201
- // Backend Info
5202
- // =========================================================================
5203
4585
  /**
5204
- * Get list of available backends
4586
+ * Return an allocation to the pool for reuse.
4587
+ *
4588
+ * `isFloat64` exists for backwards compatibility — callers can still
4589
+ * pass it positionally to disambiguate the pool to use on the AS path.
4590
+ * Prefer `releaseAllocation` for new code, which discovers the pool
4591
+ * automatically.
5205
4592
  */
5206
- getAvailableBackends() {
5207
- return backendRegistry.available();
4593
+ release(ptr, isFloat64 = true) {
4594
+ const pool = isFloat64 ? this.float64Pool : this.int32Pool;
4595
+ const entry = pool.find((e) => e.ptr === ptr);
4596
+ if (entry) {
4597
+ entry.inUse = false;
4598
+ return;
4599
+ }
4600
+ this.free(ptr);
5208
4601
  }
5209
4602
  /**
5210
- * Check if a specific backend is available
4603
+ * Free allocated memory (immediate, bypasses pool): unpin the header pointer.
5211
4604
  */
5212
- hasBackend(type) {
5213
- return backendRegistry.has(type);
4605
+ free(ptr) {
4606
+ const module = this.wasmModule;
4607
+ if (!module) return;
4608
+ this.float64Pool = this.float64Pool.filter((e) => e.ptr !== ptr);
4609
+ this.int32Pool = this.int32Pool.filter((e) => e.ptr !== ptr);
4610
+ if (typeof module.__unpin === "function") {
4611
+ module.__unpin(ptr);
4612
+ }
5214
4613
  }
5215
4614
  /**
5216
- * Get current active backend for a given operation size
4615
+ * Clear the memory pool
5217
4616
  */
5218
- getActiveBackend(elementCount, operation) {
5219
- return this.selectBackend(elementCount, operation).type;
4617
+ clearPool() {
4618
+ const module = this.wasmModule;
4619
+ if (!module) return;
4620
+ if (typeof module.__unpin === "function") {
4621
+ for (const entry of this.float64Pool) {
4622
+ module.__unpin(entry.ptr);
4623
+ }
4624
+ for (const entry of this.int32Pool) {
4625
+ module.__unpin(entry.ptr);
4626
+ }
4627
+ }
4628
+ this.float64Pool = [];
4629
+ this.int32Pool = [];
5220
4630
  }
5221
4631
  /**
5222
- * Force a specific backend for all operations
4632
+ * Get pool statistics
5223
4633
  */
5224
- forceBackend(type) {
5225
- if (type === null) {
5226
- this.hints.preferredBackend = "js";
5227
- } else {
5228
- this.hints.preferredBackend = type;
5229
- }
4634
+ getPoolStats() {
4635
+ const f64InUse = this.float64Pool.filter((e) => e.inUse).length;
4636
+ const f64Bytes = this.float64Pool.reduce((sum3, e) => sum3 + e.size, 0);
4637
+ const i32InUse = this.int32Pool.filter((e) => e.inUse).length;
4638
+ const i32Bytes = this.int32Pool.reduce((sum3, e) => sum3 + e.size, 0);
4639
+ return {
4640
+ float64: {
4641
+ total: this.float64Pool.length,
4642
+ inUse: f64InUse,
4643
+ totalBytes: f64Bytes
4644
+ },
4645
+ int32: {
4646
+ total: this.int32Pool.length,
4647
+ inUse: i32InUse,
4648
+ totalBytes: i32Bytes
4649
+ }
4650
+ };
5230
4651
  }
5231
- // =========================================================================
5232
- // Adaptive Threshold Tuning
5233
- // =========================================================================
5234
4652
  /**
5235
- * Record a performance sample for adaptive tuning
4653
+ * Run garbage collection.
5236
4654
  */
5237
- recordSample(operation, elementCount, backend, durationMs) {
5238
- const config = getConfig();
5239
- if (!config.adaptiveTuning.enabled) return;
5240
- this.adaptiveState.samples.push({
5241
- operation,
5242
- elementCount,
5243
- backend,
5244
- durationMs,
5245
- timestamp: Date.now()
5246
- });
5247
- const maxSamples = config.adaptiveTuning.sampleSize * 10;
5248
- if (this.adaptiveState.samples.length > maxSamples) {
5249
- this.adaptiveState.samples = this.adaptiveState.samples.slice(-maxSamples);
4655
+ collect() {
4656
+ const module = this.wasmModule;
4657
+ if (!module) return;
4658
+ if (typeof module.__collect === "function") {
4659
+ module.__collect();
5250
4660
  }
5251
- this.maybeAdjustThresholds();
5252
4661
  }
5253
4662
  /**
5254
- * Adjust thresholds based on collected samples
4663
+ * Reset the loader (for testing)
5255
4664
  */
5256
- maybeAdjustThresholds() {
5257
- const config = getConfig();
5258
- if (!config.adaptiveTuning.enabled) return;
5259
- const now = Date.now();
5260
- const { cooldownMs, sampleSize, minSpeedupRatio, maxAdjustmentPercent } = config.adaptiveTuning;
5261
- if (now - this.adaptiveState.lastAdjustment < cooldownMs) {
5262
- return;
5263
- }
5264
- const operationSamples = /* @__PURE__ */ new Map();
5265
- for (const sample of this.adaptiveState.samples) {
5266
- const existing = operationSamples.get(sample.operation) ?? [];
5267
- existing.push(sample);
5268
- operationSamples.set(sample.operation, existing);
5269
- }
5270
- for (const [operation, samples] of operationSamples) {
5271
- if (samples.length < sampleSize) continue;
5272
- const byBackend = /* @__PURE__ */ new Map();
5273
- for (const s of samples) {
5274
- const existing = byBackend.get(s.backend) ?? [];
5275
- existing.push(s);
5276
- byBackend.set(s.backend, existing);
5277
- }
5278
- const throughput = /* @__PURE__ */ new Map();
5279
- for (const [backend, backendSamples] of byBackend) {
5280
- const totalElements = backendSamples.reduce((s, x) => s + x.elementCount, 0);
5281
- const totalTime = backendSamples.reduce((s, x) => s + x.durationMs, 0);
5282
- if (totalTime > 0) {
5283
- throughput.set(backend, totalElements / totalTime);
5284
- }
5285
- }
5286
- const jsThroughput = throughput.get("js") ?? 0;
5287
- const wasmThroughput = throughput.get("wasm") ?? 0;
5288
- const gpuThroughput = throughput.get("gpu") ?? 0;
5289
- const currentThresholds = this.adaptiveState.adjustedThresholds.get(operation) ?? {
5290
- wasm: this.hints.operationThresholds?.[operation]?.wasm ?? this.hints.wasmThreshold,
5291
- gpu: this.hints.operationThresholds?.[operation]?.gpu ?? this.hints.gpuThreshold
5292
- };
5293
- let newWasmThreshold = currentThresholds.wasm;
5294
- let newGpuThreshold = currentThresholds.gpu;
5295
- if (wasmThroughput > jsThroughput * minSpeedupRatio) {
5296
- const adjustment = currentThresholds.wasm * (maxAdjustmentPercent / 100);
5297
- newWasmThreshold = Math.max(100, currentThresholds.wasm - adjustment);
5298
- } else if (jsThroughput > wasmThroughput * minSpeedupRatio) {
5299
- const adjustment = currentThresholds.wasm * (maxAdjustmentPercent / 100);
5300
- newWasmThreshold = currentThresholds.wasm + adjustment;
5301
- }
5302
- if (gpuThroughput > wasmThroughput * minSpeedupRatio) {
5303
- const adjustment = currentThresholds.gpu * (maxAdjustmentPercent / 100);
5304
- newGpuThreshold = Math.max(1e3, currentThresholds.gpu - adjustment);
5305
- } else if (wasmThroughput > gpuThroughput * minSpeedupRatio) {
5306
- const adjustment = currentThresholds.gpu * (maxAdjustmentPercent / 100);
5307
- newGpuThreshold = currentThresholds.gpu + adjustment;
5308
- }
5309
- this.adaptiveState.adjustedThresholds.set(operation, {
5310
- wasm: Math.round(newWasmThreshold),
5311
- gpu: Math.round(newGpuThreshold)
5312
- });
5313
- }
5314
- this.adaptiveState.lastAdjustment = now;
4665
+ reset() {
4666
+ this.clearPool();
4667
+ this.wasmModule = null;
4668
+ this.compiledModule = null;
4669
+ this.loading = null;
4670
+ this.lastMetrics = null;
5315
4671
  }
4672
+ };
4673
+ var wasmLoader = WasmLoader.getInstance();
4674
+
4675
+ // src/backends/gpu/index.ts
4676
+ import {
4677
+ hasWebGPU as hasWebGPU3,
4678
+ isBrowser,
4679
+ getGPUAdapter,
4680
+ detectGPUCapabilities as detectGPUCapabilities3,
4681
+ isGPUSuitableForMatrixOps,
4682
+ getRecommendedWorkgroupSize as getRecommendedWorkgroupSize2,
4683
+ getMaxMatrixSize,
4684
+ GPUContext as GPUContext2,
4685
+ getGlobalGPUContext as getGlobalGPUContext2,
4686
+ initializeGlobalGPU,
4687
+ destroyGlobalGPU,
4688
+ getGpuDevice,
4689
+ resetGpuDevice,
4690
+ BufferPool as BufferPool2,
4691
+ ShaderManager as ShaderManager2
4692
+ } from "@danielsimonjr/mathts-gpu";
4693
+
4694
+ // src/backends/gpu/BatchExecutor.ts
4695
+ var BatchExecutor = class {
4696
+ context;
4697
+ shaders;
4698
+ bufferPool;
4699
+ operations = [];
4700
+ options;
5316
4701
  /**
5317
- * Get current adaptive thresholds
4702
+ * Create a new batch executor
5318
4703
  */
5319
- getAdaptiveThresholds() {
5320
- return new Map(this.adaptiveState.adjustedThresholds);
4704
+ constructor(context, shaders, bufferPool, options = {}) {
4705
+ this.context = context;
4706
+ this.shaders = shaders;
4707
+ this.bufferPool = bufferPool;
4708
+ this.options = {
4709
+ maxBatchSize: options.maxBatchSize ?? 100,
4710
+ autoFlush: options.autoFlush ?? true,
4711
+ waitForCompletion: options.waitForCompletion ?? true
4712
+ };
5321
4713
  }
5322
4714
  /**
5323
- * Reset adaptive tuning state
4715
+ * Get current batch size
5324
4716
  */
5325
- resetAdaptiveState() {
5326
- this.adaptiveState = {
5327
- samples: [],
5328
- lastAdjustment: 0,
5329
- adjustedThresholds: /* @__PURE__ */ new Map()
5330
- };
4717
+ get size() {
4718
+ return this.operations.length;
5331
4719
  }
5332
4720
  /**
5333
- * Get performance statistics
4721
+ * Check if batch is empty
5334
4722
  */
5335
- getPerformanceStats() {
5336
- const operationStats = /* @__PURE__ */ new Map();
5337
- const byOperation = /* @__PURE__ */ new Map();
5338
- for (const sample of this.adaptiveState.samples) {
5339
- const existing = byOperation.get(sample.operation) ?? [];
5340
- existing.push(sample);
5341
- byOperation.set(sample.operation, existing);
5342
- }
5343
- for (const [op, samples] of byOperation) {
5344
- const avgDuration = samples.reduce((s, x) => s + x.durationMs, 0) / samples.length;
5345
- const backendUsage = {
5346
- js: 0,
5347
- wasm: 0,
5348
- gpu: 0,
5349
- parallel: 0
5350
- };
5351
- for (const s of samples) {
5352
- backendUsage[s.backend]++;
5353
- }
5354
- operationStats.set(op, {
5355
- avgDuration,
5356
- samples: samples.length,
5357
- backendUsage
5358
- });
5359
- }
5360
- return {
5361
- sampleCount: this.adaptiveState.samples.length,
5362
- operationStats
5363
- };
4723
+ get isEmpty() {
4724
+ return this.operations.length === 0;
5364
4725
  }
5365
4726
  /**
5366
- * Cleanup resources
4727
+ * Check if batch is full
5367
4728
  */
5368
- destroy() {
5369
- if (this.configUnsubscribe) {
5370
- this.configUnsubscribe();
5371
- this.configUnsubscribe = null;
5372
- }
4729
+ get isFull() {
4730
+ return this.operations.length >= this.options.maxBatchSize;
5373
4731
  }
5374
- };
5375
- var backendManager = new BackendManager();
5376
- function createBackendManager(hints) {
5377
- return new BackendManager(hints);
5378
- }
5379
-
5380
- // src/backends/WasmLoader.ts
5381
- var AS_ID_ARRAY_BUFFER2 = 1;
5382
- var AS_ID_INT32_ARRAY2 = 4;
5383
- var AS_ID_FLOAT64_ARRAY2 = 5;
5384
- var AS_HEADER_BYTES2 = 12;
5385
- var WasmLoader = class _WasmLoader {
5386
- static instance = null;
5387
- wasmModule = null;
5388
- compiledModule = null;
5389
- loading = null;
5390
- isNode;
5391
- lastMetrics = null;
5392
- // Memory pool for AS allocations.
5393
- float64Pool = [];
5394
- int32Pool = [];
5395
- poolSizeThreshold = 1024 * 1024;
5396
- // 1MB max per pool entry
5397
- constructor() {
5398
- this.isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
4732
+ /**
4733
+ * Queue an add operation
4734
+ */
4735
+ add(inputA, inputB, output, dimensions) {
4736
+ this.queueOperation({
4737
+ type: "add",
4738
+ inputA,
4739
+ inputB,
4740
+ output,
4741
+ dimensions
4742
+ });
5399
4743
  }
5400
- static getInstance() {
5401
- if (!_WasmLoader.instance) {
5402
- _WasmLoader.instance = new _WasmLoader();
5403
- }
5404
- return _WasmLoader.instance;
4744
+ /**
4745
+ * Queue a subtract operation
4746
+ */
4747
+ subtract(inputA, inputB, output, dimensions) {
4748
+ this.queueOperation({
4749
+ type: "subtract",
4750
+ inputA,
4751
+ inputB,
4752
+ output,
4753
+ dimensions
4754
+ });
5405
4755
  }
5406
4756
  /**
5407
- * Load the WASM module
4757
+ * Queue an element-wise multiply operation
5408
4758
  */
5409
- async load(wasmPath) {
5410
- if (this.wasmModule) {
5411
- return this.wasmModule;
5412
- }
5413
- if (this.loading) {
5414
- return this.loading;
5415
- }
5416
- this.loading = this.loadModule(wasmPath);
5417
- this.wasmModule = await this.loading;
5418
- return this.wasmModule;
4759
+ multiply(inputA, inputB, output, dimensions) {
4760
+ this.queueOperation({
4761
+ type: "multiply",
4762
+ inputA,
4763
+ inputB,
4764
+ output,
4765
+ dimensions
4766
+ });
5419
4767
  }
5420
4768
  /**
5421
- * Precompile the WASM module without instantiation
5422
- * Useful for build-time or startup optimization
4769
+ * Queue a scale operation
5423
4770
  */
5424
- async precompile(wasmPath) {
5425
- if (this.compiledModule) return;
5426
- const path = wasmPath || await this.getDefaultWasmPath();
5427
- const startTime = performance.now();
5428
- const { loadWasmManifest, verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
5429
- if (this.isNode) {
5430
- const fs = await import("fs");
5431
- const { promisify } = await import("util");
5432
- const readFile = promisify(fs.readFile);
5433
- const buffer = await readFile(path);
5434
- await verifyWasmIntegrity(buffer, path);
5435
- this.compiledModule = await WebAssembly.compile(buffer);
5436
- } else {
5437
- const manifest = await loadWasmManifest(path);
5438
- if (!manifest && typeof WebAssembly.compileStreaming === "function") {
5439
- this.compiledModule = await WebAssembly.compileStreaming(fetch(path));
5440
- } else {
5441
- const response = await fetch(path);
5442
- const buffer = await response.arrayBuffer();
5443
- await verifyWasmIntegrity(buffer, path, { manifest });
5444
- this.compiledModule = await WebAssembly.compile(buffer);
5445
- }
5446
- }
5447
- this.lastMetrics = {
5448
- fileReadMs: 0,
5449
- compileMs: performance.now() - startTime,
5450
- instantiateMs: 0,
5451
- totalMs: performance.now() - startTime,
5452
- fromCache: false
5453
- };
4771
+ scale(input, output, scalar, dimensions) {
4772
+ this.queueOperation({
4773
+ type: "scale",
4774
+ inputA: input,
4775
+ output,
4776
+ dimensions,
4777
+ scalar
4778
+ });
5454
4779
  }
5455
- async loadModule(wasmPath) {
5456
- const path = wasmPath || await this.getDefaultWasmPath();
5457
- const totalStart = performance.now();
5458
- if (this.compiledModule) {
5459
- const instStart = performance.now();
5460
- const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
5461
- this.lastMetrics = {
5462
- fileReadMs: 0,
5463
- compileMs: 0,
5464
- instantiateMs: performance.now() - instStart,
5465
- totalMs: performance.now() - totalStart,
5466
- fromCache: true
5467
- };
5468
- return instance.exports;
5469
- }
5470
- if (this.isNode) {
5471
- return this.loadNodeWasm(path, totalStart);
5472
- } else {
5473
- return this.loadBrowserWasm(path, totalStart);
5474
- }
4780
+ /**
4781
+ * Queue a matrix multiplication operation
4782
+ */
4783
+ matmul(inputA, inputB, output, dimensions) {
4784
+ this.queueOperation({
4785
+ type: "matmul",
4786
+ inputA,
4787
+ inputB,
4788
+ output,
4789
+ dimensions
4790
+ });
5475
4791
  }
5476
4792
  /**
5477
- * Get the WASM binary path. AssemblyScript is the sole WASM backend
5478
- * (`mathts-as.wasm`); the legacy second toolchain was removed in the
5479
- * WASM-backend migration (complete 2026-06-26).
5480
- *
5481
- * Resolution is package-relative and CWD-independent: the packaged artifact
5482
- * (`dist/wasm/`) is preferred via `resolvePackagedWasm`; when absent, the
5483
- * canonical expected location is returned so the missing-binary warning is
5484
- * actionable (see `defaultWasmLocation` in wasm/resolve.ts — B-3).
4793
+ * Queue a transpose operation
5485
4794
  */
5486
- async getDefaultWasmPath() {
5487
- const wasmFile = "mathts-as.wasm";
5488
- if (this.isNode) {
5489
- const { resolvePackagedWasm } = await import("./resolve-VJX3YTS6.js");
5490
- const found = await resolvePackagedWasm(import.meta.url, wasmFile);
5491
- if (found) return found;
5492
- const { defaultWasmLocation: defaultWasmLocation2 } = await import("./resolve-VJX3YTS6.js");
5493
- return defaultWasmLocation2(import.meta.url, wasmFile);
5494
- }
5495
- const { defaultWasmLocation } = await import("./resolve-VJX3YTS6.js");
5496
- return defaultWasmLocation(import.meta.url, wasmFile, { browser: true });
4795
+ transpose(input, output, dimensions) {
4796
+ this.queueOperation({
4797
+ type: "transpose",
4798
+ inputA: input,
4799
+ output,
4800
+ dimensions
4801
+ });
5497
4802
  }
5498
- async loadNodeWasm(path, totalStart) {
5499
- const fs = await import("fs");
5500
- const { promisify } = await import("util");
5501
- const readFile = promisify(fs.readFile);
5502
- const { verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
5503
- const readStart = performance.now();
5504
- const buffer = await readFile(path);
5505
- const readEnd = performance.now();
5506
- await verifyWasmIntegrity(buffer, path);
5507
- const compileStart = performance.now();
5508
- this.compiledModule = await WebAssembly.compile(buffer);
5509
- const compileEnd = performance.now();
5510
- const instStart = performance.now();
5511
- const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
5512
- const instEnd = performance.now();
5513
- this.lastMetrics = {
5514
- fileReadMs: readEnd - readStart,
5515
- compileMs: compileEnd - compileStart,
5516
- instantiateMs: instEnd - instStart,
5517
- totalMs: performance.now() - totalStart,
5518
- fromCache: false
5519
- };
5520
- return instance.exports;
4803
+ /**
4804
+ * Queue a sum reduction operation
4805
+ */
4806
+ reduceSum(input, output, dimensions) {
4807
+ this.queueOperation({
4808
+ type: "reduce_sum",
4809
+ inputA: input,
4810
+ output,
4811
+ dimensions
4812
+ });
5521
4813
  }
5522
- async loadBrowserWasm(path, totalStart) {
5523
- const { loadWasmManifest, verifyWasmIntegrity } = await import("./integrity-X3CLNVGW.js");
5524
- const manifest = await loadWasmManifest(path);
5525
- if (!manifest && typeof WebAssembly.instantiateStreaming === "function") {
5526
- const instStart2 = performance.now();
5527
- const result = await WebAssembly.instantiateStreaming(fetch(path), this.getImports());
5528
- this.compiledModule = result.module;
5529
- this.lastMetrics = {
5530
- fileReadMs: 0,
5531
- // Combined with compile in streaming
5532
- compileMs: 0,
5533
- // Combined in streaming
5534
- instantiateMs: performance.now() - instStart2,
5535
- totalMs: performance.now() - totalStart,
5536
- fromCache: false
5537
- };
5538
- return result.instance.exports;
4814
+ /**
4815
+ * Queue operation (internal)
4816
+ */
4817
+ queueOperation(op) {
4818
+ this.operations.push(op);
4819
+ if (this.options.autoFlush && this.isFull) {
4820
+ this.flushSync();
5539
4821
  }
5540
- const readStart = performance.now();
5541
- const response = await fetch(path);
5542
- const buffer = await response.arrayBuffer();
5543
- const readEnd = performance.now();
5544
- await verifyWasmIntegrity(buffer, path, { manifest });
5545
- const compileStart = performance.now();
5546
- this.compiledModule = await WebAssembly.compile(buffer);
5547
- const compileEnd = performance.now();
5548
- const instStart = performance.now();
5549
- const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
5550
- const instEnd = performance.now();
5551
- this.lastMetrics = {
5552
- fileReadMs: readEnd - readStart,
5553
- compileMs: compileEnd - compileStart,
5554
- instantiateMs: instEnd - instStart,
5555
- totalMs: performance.now() - totalStart,
5556
- fromCache: false
5557
- };
5558
- return instance.exports;
5559
- }
5560
- getImports() {
5561
- return {
5562
- env: {
5563
- abort: (msg, file, line, column2) => {
5564
- console.error("WASM abort", { msg, file, line, column: column2 });
5565
- throw new Error("WASM abort");
5566
- },
5567
- seed: () => Date.now()
5568
- },
5569
- Math,
5570
- Date
5571
- };
5572
4822
  }
5573
4823
  /**
5574
- * Get the loaded WASM module
4824
+ * Flush all queued operations (async)
5575
4825
  */
5576
- getModule() {
5577
- return this.wasmModule;
4826
+ async flush() {
4827
+ if (this.isEmpty) {
4828
+ return {
4829
+ success: true,
4830
+ operationCount: 0,
4831
+ duration: 0
4832
+ };
4833
+ }
4834
+ const start = performance.now();
4835
+ const operationCount = this.operations.length;
4836
+ try {
4837
+ const encoder = this.context.createCommandEncoder();
4838
+ for (const op of this.operations) {
4839
+ this.encodeOperation(encoder, op);
4840
+ }
4841
+ const commandBuffer = encoder.finish();
4842
+ this.context.submitCommands([commandBuffer]);
4843
+ if (this.options.waitForCompletion) {
4844
+ await this.context.getDevice().queue.onSubmittedWorkDone();
4845
+ }
4846
+ this.operations = [];
4847
+ return {
4848
+ success: true,
4849
+ operationCount,
4850
+ duration: performance.now() - start
4851
+ };
4852
+ } catch (error) {
4853
+ return {
4854
+ success: false,
4855
+ operationCount,
4856
+ duration: performance.now() - start,
4857
+ error: error instanceof Error ? error.message : String(error)
4858
+ };
4859
+ }
5578
4860
  }
5579
4861
  /**
5580
- * Get the compiled WASM module (for caching/serialization)
4862
+ * Flush synchronously (fire and forget)
5581
4863
  */
5582
- getCompiledModule() {
5583
- return this.compiledModule;
4864
+ flushSync() {
4865
+ if (this.isEmpty) return;
4866
+ try {
4867
+ const encoder = this.context.createCommandEncoder();
4868
+ for (const op of this.operations) {
4869
+ this.encodeOperation(encoder, op);
4870
+ }
4871
+ const commandBuffer = encoder.finish();
4872
+ this.context.submitCommands([commandBuffer]);
4873
+ this.operations = [];
4874
+ } catch {
4875
+ }
5584
4876
  }
5585
4877
  /**
5586
- * Check if WASM is loaded
4878
+ * Encode a single operation into the command encoder
5587
4879
  */
5588
- isLoaded() {
5589
- return this.wasmModule !== null;
4880
+ encodeOperation(encoder, op) {
4881
+ const pipelineName = this.getPipelineName(op.type);
4882
+ const pipeline = this.shaders.getRegisteredPipeline(pipelineName);
4883
+ const entries = [{ binding: 0, resource: { buffer: op.inputA } }];
4884
+ if (op.inputB) {
4885
+ entries.push({ binding: 1, resource: { buffer: op.inputB } });
4886
+ }
4887
+ entries.push({
4888
+ binding: op.inputB ? 2 : 1,
4889
+ resource: { buffer: op.output }
4890
+ });
4891
+ const params = this.createParamsBuffer(op);
4892
+ entries.push({
4893
+ binding: entries.length,
4894
+ resource: { buffer: params }
4895
+ });
4896
+ const bindGroup = this.context.getDevice().createBindGroup({
4897
+ layout: pipeline.getBindGroupLayout(0),
4898
+ entries
4899
+ });
4900
+ const passEncoder = encoder.beginComputePass();
4901
+ passEncoder.setPipeline(pipeline);
4902
+ passEncoder.setBindGroup(0, bindGroup);
4903
+ const [wgX, wgY, wgZ] = this.calculateWorkgroups(op);
4904
+ passEncoder.dispatchWorkgroups(wgX, wgY, wgZ);
4905
+ passEncoder.end();
5590
4906
  }
5591
4907
  /**
5592
- * Check if WASM is precompiled
4908
+ * Get pipeline name for operation type
5593
4909
  */
5594
- isPrecompiled() {
5595
- return this.compiledModule !== null;
4910
+ getPipelineName(type) {
4911
+ const mapping = {
4912
+ add: "matrixAdd",
4913
+ subtract: "matrixSub",
4914
+ multiply: "matrixMul",
4915
+ divide: "matrixDiv",
4916
+ scale: "scalarMul",
4917
+ matmul: "matmul",
4918
+ transpose: "transpose",
4919
+ reduce_sum: "sumReduce",
4920
+ reduce_max: "maxReduce",
4921
+ reduce_min: "minReduce"
4922
+ };
4923
+ return mapping[type] || "matrixAdd";
5596
4924
  }
5597
4925
  /**
5598
- * Get loading performance metrics
4926
+ * Create params buffer for operation
5599
4927
  */
5600
- getLoadingMetrics() {
5601
- return this.lastMetrics;
4928
+ createParamsBuffer(op) {
4929
+ const { rows, cols, k } = op.dimensions;
4930
+ let data;
4931
+ if (op.type === "matmul" && k !== void 0) {
4932
+ data = new Uint32Array([rows, cols, k, 0]);
4933
+ } else if (op.type === "scale" && op.scalar !== void 0) {
4934
+ const floatArray = new Float32Array([op.scalar]);
4935
+ const uintView = new Uint32Array(floatArray.buffer);
4936
+ data = new Uint32Array([rows, cols, uintView[0], 0]);
4937
+ } else {
4938
+ data = new Uint32Array([rows, cols, 0, 0]);
4939
+ }
4940
+ const buffer = this.bufferPool.acquireUniformBuffer(16, "batch-params");
4941
+ this.context.writeBuffer(buffer, data);
4942
+ return buffer;
5602
4943
  }
5603
- // ===========================================================================
5604
- // Allocation API
5605
- // ---------------------------------------------------------------------------
5606
- // The returned handle's `ptr` is the header pointer to pass to WASM
5607
- // functions (AssemblyScript managed runtime).
5608
- // ===========================================================================
5609
4944
  /**
5610
- * Allocate Float64Array in WASM memory and copy `data` into it.
5611
- * Uses memory pooling.
4945
+ * Calculate workgroup dispatch counts
5612
4946
  */
5613
- allocateFloat64Array(data) {
5614
- const module = this.wasmModule;
5615
- if (!module) throw new Error("WASM module not loaded");
5616
- const length = data.length;
5617
- const alloc = this.allocateAsFloat64(module, length);
5618
- alloc.array.set(data);
5619
- return alloc;
4947
+ calculateWorkgroups(op) {
4948
+ const { rows, cols } = op.dimensions;
4949
+ const workgroupSize = 16;
4950
+ if (op.type === "reduce_sum" || op.type === "reduce_max" || op.type === "reduce_min") {
4951
+ const total = rows * cols;
4952
+ return [Math.ceil(total / 256), 1, 1];
4953
+ }
4954
+ return [Math.ceil(cols / workgroupSize), Math.ceil(rows / workgroupSize), 1];
5620
4955
  }
5621
4956
  /**
5622
- * Decode a managed Float64Array RETURNED by an AssemblyScript export
5623
- * (e.g. the `matrix_lu_decompose` / `matrix_qr_decompose` outputs) into a fresh JS copy.
5624
- *
5625
- * AS exports that return a `Float64Array` hand back the typed-array
5626
- * *header* pointer. The header is a 12-byte block laid out as
5627
- * `[buffer, dataStart, byteLength]` (little-endian u32s) — the same shape
5628
- * the loader writes in `makeAsFloat64`. We read `dataStart`/`byteLength`
5629
- * and copy the data region out so it survives any later reuse of WASM
5630
- * memory.
4957
+ * Clear all queued operations without executing
5631
4958
  */
5632
- readReturnedFloat64Array(headerPtr) {
5633
- const module = this.wasmModule;
5634
- if (!module) throw new Error("WASM module not loaded");
5635
- const hdr = headerPtr >>> 0;
5636
- const dv = new DataView(module.memory.buffer);
5637
- const dataStart = dv.getUint32(hdr + 4, true);
5638
- const byteLength = dv.getUint32(hdr + 8, true);
5639
- const view = new Float64Array(module.memory.buffer, dataStart, byteLength >>> 3);
5640
- return new Float64Array(view);
4959
+ clear() {
4960
+ this.operations = [];
5641
4961
  }
5642
4962
  /**
5643
- * Allocate Float64Array without copying data (for output buffers)
4963
+ * Get statistics about the batch executor
5644
4964
  */
5645
- allocateFloat64ArrayEmpty(length) {
5646
- const module = this.wasmModule;
5647
- if (!module) throw new Error("WASM module not loaded");
5648
- return this.allocateAsFloat64(module, length);
4965
+ getStats() {
4966
+ return {
4967
+ queuedOperations: this.operations.length,
4968
+ maxBatchSize: this.options.maxBatchSize,
4969
+ autoFlush: this.options.autoFlush
4970
+ };
5649
4971
  }
5650
- /**
5651
- * Allocate Int32Array in WASM memory and copy `data` into it.
5652
- */
5653
- allocateInt32Array(data) {
5654
- const module = this.wasmModule;
5655
- if (!module) throw new Error("WASM module not loaded");
5656
- const length = data.length;
5657
- const alloc = this.allocateAsInt32(module, length);
5658
- alloc.array.set(data);
5659
- return alloc;
4972
+ };
4973
+
4974
+ // src/backends/gpu/Sync.ts
4975
+ var SyncManager = class {
4976
+ context;
4977
+ config;
4978
+ pendingTransfers = /* @__PURE__ */ new Map();
4979
+ nextRequestId = 0;
4980
+ stagingBuffers = [];
4981
+ // Statistics
4982
+ totalUploads = 0;
4983
+ totalDownloads = 0;
4984
+ totalBytesUploaded = 0;
4985
+ totalBytesDownloaded = 0;
4986
+ constructor(context, _bufferPool, config = {}) {
4987
+ this.context = context;
4988
+ this.config = {
4989
+ strategy: config.strategy ?? "lazy",
4990
+ chunkSize: config.chunkSize ?? 1024 * 1024,
4991
+ // 1MB default
4992
+ maxPendingTransfers: config.maxPendingTransfers ?? 16,
4993
+ coalesceTransfers: config.coalesceTransfers ?? true
4994
+ };
5660
4995
  }
5661
4996
  /**
5662
- * Allocate Int32Array without copying data (for output buffers)
4997
+ * Upload data from CPU to GPU
5663
4998
  */
5664
- allocateInt32ArrayEmpty(length) {
5665
- const module = this.wasmModule;
5666
- if (!module) throw new Error("WASM module not loaded");
5667
- return this.allocateAsInt32(module, length);
5668
- }
5669
- // ---- AS managed runtime (header pointers) ------------------------------
5670
- allocateAsFloat64(module, length) {
5671
- const byteLength = length * 8;
5672
- if (byteLength <= this.poolSizeThreshold) {
5673
- const recycled = this.acquireFromAsPool(this.float64Pool, byteLength);
5674
- if (recycled) {
5675
- const array = new Float64Array(module.memory.buffer, recycled.dataPtr, length);
5676
- return {
5677
- ptr: recycled.ptr,
5678
- dataPtr: recycled.dataPtr,
5679
- array,
5680
- length
5681
- };
5682
- }
5683
- }
5684
- return this.makeAsFloat64(module, length);
5685
- }
5686
- allocateAsInt32(module, length) {
5687
- const byteLength = length * 4;
5688
- if (byteLength <= this.poolSizeThreshold) {
5689
- const recycled = this.acquireFromAsPool(this.int32Pool, byteLength);
5690
- if (recycled) {
5691
- const array = new Int32Array(module.memory.buffer, recycled.dataPtr, length);
5692
- return {
5693
- ptr: recycled.ptr,
5694
- dataPtr: recycled.dataPtr,
5695
- array,
5696
- length
5697
- };
4999
+ async upload(cpuData, gpuBuffer, options = {}) {
5000
+ const id = this.nextRequestId++;
5001
+ const start = performance.now();
5002
+ try {
5003
+ const offset = options.offset ?? 0;
5004
+ const size2 = options.size ?? cpuData.byteLength;
5005
+ this.context.writeBuffer(gpuBuffer, cpuData, offset);
5006
+ if (this.config.strategy === "immediate") {
5007
+ await this.context.getDevice().queue.onSubmittedWorkDone();
5698
5008
  }
5699
- }
5700
- return this.makeAsInt32(module, length);
5701
- }
5702
- makeAsFloat64(module, length) {
5703
- const byteLength = length * 8;
5704
- const bufferPtr = module.__pin(module.__new(byteLength, AS_ID_ARRAY_BUFFER2)) >>> 0;
5705
- const headerPtr = module.__new(AS_HEADER_BYTES2, AS_ID_FLOAT64_ARRAY2) >>> 0;
5706
- module.__pin(headerPtr);
5707
- const dv = new DataView(module.memory.buffer);
5708
- dv.setUint32(headerPtr + 0, bufferPtr, true);
5709
- dv.setUint32(headerPtr + 4, bufferPtr, true);
5710
- dv.setUint32(headerPtr + 8, byteLength, true);
5711
- const array = new Float64Array(module.memory.buffer, bufferPtr, length);
5712
- return { ptr: headerPtr, dataPtr: bufferPtr, array, length };
5009
+ this.totalUploads++;
5010
+ this.totalBytesUploaded += size2;
5011
+ return {
5012
+ id,
5013
+ success: true,
5014
+ duration: performance.now() - start,
5015
+ bytesTransferred: size2
5016
+ };
5017
+ } catch (error) {
5018
+ return {
5019
+ id,
5020
+ success: false,
5021
+ duration: performance.now() - start,
5022
+ bytesTransferred: 0,
5023
+ error: error instanceof Error ? error.message : String(error)
5024
+ };
5025
+ }
5713
5026
  }
5714
- makeAsInt32(module, length) {
5715
- const byteLength = length * 4;
5716
- const bufferPtr = module.__pin(module.__new(byteLength, AS_ID_ARRAY_BUFFER2)) >>> 0;
5717
- const headerPtr = module.__new(AS_HEADER_BYTES2, AS_ID_INT32_ARRAY2) >>> 0;
5718
- module.__pin(headerPtr);
5719
- const dv = new DataView(module.memory.buffer);
5720
- dv.setUint32(headerPtr + 0, bufferPtr, true);
5721
- dv.setUint32(headerPtr + 4, bufferPtr, true);
5722
- dv.setUint32(headerPtr + 8, byteLength, true);
5723
- const array = new Int32Array(module.memory.buffer, bufferPtr, length);
5724
- return { ptr: headerPtr, dataPtr: bufferPtr, array, length };
5027
+ /**
5028
+ * Download data from GPU to CPU
5029
+ */
5030
+ async download(gpuBuffer, options = {}) {
5031
+ const offset = options.offset ?? 0;
5032
+ const size2 = options.size ?? gpuBuffer.size;
5033
+ this.totalDownloads++;
5034
+ this.totalBytesDownloaded += size2;
5035
+ const buffer = await this.context.readBuffer(gpuBuffer, offset, size2);
5036
+ return new Float32Array(buffer);
5725
5037
  }
5726
5038
  /**
5727
- * Find a pool entry whose data region is large enough; returns null when
5728
- * no suitable entry is available.
5039
+ * Download data using double-buffering for overlap
5729
5040
  */
5730
- acquireFromAsPool(pool, requestedSize) {
5731
- let bestFit = null;
5732
- let bestFitWaste = Infinity;
5733
- for (const entry of pool) {
5734
- if (!entry.inUse && entry.size >= requestedSize) {
5735
- const waste = entry.size - requestedSize;
5736
- if (waste < bestFitWaste && entry.size <= requestedSize * 2) {
5737
- bestFit = entry;
5738
- bestFitWaste = waste;
5041
+ async downloadDoubleBuffered(gpuBuffer, size2) {
5042
+ const staging1 = this.getOrCreateStagingBuffer(size2);
5043
+ const encoder = this.context.createCommandEncoder();
5044
+ encoder.copyBufferToBuffer(gpuBuffer, 0, staging1, 0, size2);
5045
+ this.context.submitCommands([encoder.finish()]);
5046
+ await staging1.mapAsync(GPUMapMode.READ);
5047
+ const data = new Float32Array(staging1.getMappedRange().slice(0));
5048
+ staging1.unmap();
5049
+ return data;
5050
+ }
5051
+ /**
5052
+ * Stream large data in chunks
5053
+ */
5054
+ async uploadStreaming(cpuData, gpuBuffer, onProgress) {
5055
+ const id = this.nextRequestId++;
5056
+ const start = performance.now();
5057
+ const chunkSize = this.config.chunkSize / 4;
5058
+ const totalElements = cpuData.length;
5059
+ let transferred = 0;
5060
+ try {
5061
+ for (let offset = 0; offset < totalElements; offset += chunkSize) {
5062
+ const end = Math.min(offset + chunkSize, totalElements);
5063
+ const chunk = cpuData.subarray(offset, end);
5064
+ this.context.writeBuffer(
5065
+ gpuBuffer,
5066
+ chunk,
5067
+ offset * 4
5068
+ // byte offset
5069
+ );
5070
+ transferred = end;
5071
+ if (onProgress) {
5072
+ onProgress(transferred / totalElements);
5739
5073
  }
5074
+ await new Promise((resolve) => setTimeout(resolve, 0));
5740
5075
  }
5076
+ await this.context.getDevice().queue.onSubmittedWorkDone();
5077
+ this.totalUploads++;
5078
+ this.totalBytesUploaded += totalElements * 4;
5079
+ return {
5080
+ id,
5081
+ success: true,
5082
+ duration: performance.now() - start,
5083
+ bytesTransferred: totalElements * 4
5084
+ };
5085
+ } catch (error) {
5086
+ return {
5087
+ id,
5088
+ success: false,
5089
+ duration: performance.now() - start,
5090
+ bytesTransferred: transferred * 4,
5091
+ error: error instanceof Error ? error.message : String(error)
5092
+ };
5741
5093
  }
5742
- if (bestFit) bestFit.inUse = true;
5743
- return bestFit;
5744
5094
  }
5745
5095
  /**
5746
- * Return an allocation to the pool for reuse.
5747
- *
5748
- * `isFloat64` exists for backwards compatibility — callers can still
5749
- * pass it positionally to disambiguate the pool to use on the AS path.
5750
- * Prefer `releaseAllocation` for new code, which discovers the pool
5751
- * automatically.
5096
+ * Download large data in chunks
5752
5097
  */
5753
- release(ptr, isFloat64 = true) {
5754
- const pool = isFloat64 ? this.float64Pool : this.int32Pool;
5755
- const entry = pool.find((e) => e.ptr === ptr);
5756
- if (entry) {
5757
- entry.inUse = false;
5758
- return;
5098
+ async downloadStreaming(gpuBuffer, totalSize, onProgress) {
5099
+ const result = new Float32Array(totalSize / 4);
5100
+ const chunkSize = this.config.chunkSize;
5101
+ let downloaded = 0;
5102
+ while (downloaded < totalSize) {
5103
+ const remaining = totalSize - downloaded;
5104
+ const currentChunkSize = Math.min(chunkSize, remaining);
5105
+ const chunk = await this.download(gpuBuffer, {
5106
+ offset: downloaded,
5107
+ size: currentChunkSize
5108
+ });
5109
+ result.set(chunk, downloaded / 4);
5110
+ downloaded += currentChunkSize;
5111
+ if (onProgress) {
5112
+ onProgress(downloaded / totalSize);
5113
+ }
5759
5114
  }
5760
- this.free(ptr);
5115
+ return result;
5761
5116
  }
5762
5117
  /**
5763
- * Free allocated memory (immediate, bypasses pool): unpin the header pointer.
5118
+ * Batch multiple transfers
5764
5119
  */
5765
- free(ptr) {
5766
- const module = this.wasmModule;
5767
- if (!module) return;
5768
- this.float64Pool = this.float64Pool.filter((e) => e.ptr !== ptr);
5769
- this.int32Pool = this.int32Pool.filter((e) => e.ptr !== ptr);
5770
- if (typeof module.__unpin === "function") {
5771
- module.__unpin(ptr);
5120
+ async batchTransfer(requests) {
5121
+ const results = [];
5122
+ const start = performance.now();
5123
+ const uploads = requests.filter((r) => r.direction === "cpu-to-gpu");
5124
+ const downloads = requests.filter((r) => r.direction === "gpu-to-cpu");
5125
+ for (const req of uploads) {
5126
+ const result = await this.upload(req.data, req.buffer);
5127
+ results.push(result);
5128
+ }
5129
+ for (const req of downloads) {
5130
+ const id = this.nextRequestId++;
5131
+ try {
5132
+ const data = await this.download(req.buffer);
5133
+ req.data.set(data);
5134
+ results.push({
5135
+ id,
5136
+ success: true,
5137
+ duration: performance.now() - start,
5138
+ bytesTransferred: data.byteLength
5139
+ });
5140
+ } catch (error) {
5141
+ results.push({
5142
+ id,
5143
+ success: false,
5144
+ duration: performance.now() - start,
5145
+ bytesTransferred: 0,
5146
+ error: error instanceof Error ? error.message : String(error)
5147
+ });
5148
+ }
5772
5149
  }
5150
+ return results;
5773
5151
  }
5774
5152
  /**
5775
- * Clear the memory pool
5153
+ * Create or reuse a staging buffer
5776
5154
  */
5777
- clearPool() {
5778
- const module = this.wasmModule;
5779
- if (!module) return;
5780
- if (typeof module.__unpin === "function") {
5781
- for (const entry of this.float64Pool) {
5782
- module.__unpin(entry.ptr);
5783
- }
5784
- for (const entry of this.int32Pool) {
5785
- module.__unpin(entry.ptr);
5155
+ getOrCreateStagingBuffer(size2) {
5156
+ const alignedSize = this.roundToPowerOf2(size2);
5157
+ for (const buffer2 of this.stagingBuffers) {
5158
+ if (buffer2.size >= alignedSize) {
5159
+ return buffer2;
5786
5160
  }
5787
5161
  }
5788
- this.float64Pool = [];
5789
- this.int32Pool = [];
5162
+ const buffer = this.context.createStagingBuffer(alignedSize, "sync-staging");
5163
+ this.stagingBuffers.push(buffer);
5164
+ return buffer;
5790
5165
  }
5791
5166
  /**
5792
- * Get pool statistics
5167
+ * Round up to next power of 2
5793
5168
  */
5794
- getPoolStats() {
5795
- const f64InUse = this.float64Pool.filter((e) => e.inUse).length;
5796
- const f64Bytes = this.float64Pool.reduce((sum3, e) => sum3 + e.size, 0);
5797
- const i32InUse = this.int32Pool.filter((e) => e.inUse).length;
5798
- const i32Bytes = this.int32Pool.reduce((sum3, e) => sum3 + e.size, 0);
5799
- return {
5800
- float64: {
5801
- total: this.float64Pool.length,
5802
- inUse: f64InUse,
5803
- totalBytes: f64Bytes
5804
- },
5805
- int32: {
5806
- total: this.int32Pool.length,
5807
- inUse: i32InUse,
5808
- totalBytes: i32Bytes
5809
- }
5810
- };
5169
+ roundToPowerOf2(n) {
5170
+ if (n <= 0) return 256;
5171
+ n--;
5172
+ n |= n >> 1;
5173
+ n |= n >> 2;
5174
+ n |= n >> 4;
5175
+ n |= n >> 8;
5176
+ n |= n >> 16;
5177
+ return n + 1;
5811
5178
  }
5812
5179
  /**
5813
- * Run garbage collection.
5180
+ * Wait for all pending transfers to complete
5814
5181
  */
5815
- collect() {
5816
- const module = this.wasmModule;
5817
- if (!module) return;
5818
- if (typeof module.__collect === "function") {
5819
- module.__collect();
5820
- }
5182
+ async flush() {
5183
+ await this.context.getDevice().queue.onSubmittedWorkDone();
5184
+ await Promise.all(this.pendingTransfers.values());
5185
+ this.pendingTransfers.clear();
5821
5186
  }
5822
5187
  /**
5823
- * Reset the loader (for testing)
5188
+ * Get synchronization statistics
5824
5189
  */
5825
- reset() {
5826
- this.clearPool();
5827
- this.wasmModule = null;
5828
- this.compiledModule = null;
5829
- this.loading = null;
5830
- this.lastMetrics = null;
5190
+ getStats() {
5191
+ return {
5192
+ totalUploads: this.totalUploads,
5193
+ totalDownloads: this.totalDownloads,
5194
+ totalBytesUploaded: this.totalBytesUploaded,
5195
+ totalBytesDownloaded: this.totalBytesDownloaded,
5196
+ pendingTransfers: this.pendingTransfers.size,
5197
+ stagingBuffersCount: this.stagingBuffers.length,
5198
+ strategy: this.config.strategy
5199
+ };
5200
+ }
5201
+ /**
5202
+ * Destroy sync manager and release resources
5203
+ */
5204
+ destroy() {
5205
+ for (const buffer of this.stagingBuffers) {
5206
+ buffer.destroy();
5207
+ }
5208
+ this.stagingBuffers = [];
5209
+ this.pendingTransfers.clear();
5831
5210
  }
5832
5211
  };
5833
- var wasmLoader = WasmLoader.getInstance();
5212
+ function createSyncManager(context, bufferPool, strategy = "lazy") {
5213
+ return new SyncManager(context, bufferPool, { strategy });
5214
+ }
5834
5215
 
5835
5216
  // src/operations/common.ts
5836
5217
  function eye(n) {
@@ -8816,17 +8197,17 @@ export {
8816
8197
  BackendManager,
8817
8198
  BackendRegistry,
8818
8199
  BatchExecutor,
8819
- BufferPool,
8200
+ BufferPool2 as BufferPool,
8820
8201
  DEFAULT_BACKEND_HINTS,
8821
8202
  DEFAULT_EXTENDED_HINTS,
8822
8203
  DenseMatrix,
8823
8204
  GPUBackend,
8824
- GPUContext,
8205
+ GPUContext2 as GPUContext,
8825
8206
  GPUMatrixBackend,
8826
8207
  JSBackend,
8827
8208
  Matrix,
8828
8209
  ParallelBackend,
8829
- ShaderManager,
8210
+ ShaderManager2 as ShaderManager,
8830
8211
  SparseMatrix,
8831
8212
  SyncManager,
8832
8213
  WASMBackend,
@@ -8845,7 +8226,7 @@ export {
8845
8226
  createWASMBackend,
8846
8227
  destroyGlobalGPU,
8847
8228
  destroyGlobalGPUBackend,
8848
- detectGPUCapabilities,
8229
+ detectGPUCapabilities3 as detectGPUCapabilities,
8849
8230
  detectWasmFeatures,
8850
8231
  diag,
8851
8232
  diagonal,
@@ -8858,10 +8239,10 @@ export {
8858
8239
  exp,
8859
8240
  getCachedFeatures,
8860
8241
  getGlobalGPUBackend,
8861
- getGlobalGPUContext,
8862
- getRecommendedWorkgroupSize,
8242
+ getGlobalGPUContext2 as getGlobalGPUContext,
8243
+ getRecommendedWorkgroupSize2 as getRecommendedWorkgroupSize,
8863
8244
  gpuMatrixBackend,
8864
- hasWebGPU,
8245
+ hasWebGPU3 as hasWebGPU,
8865
8246
  identity,
8866
8247
  initializeGlobalGPUBackend,
8867
8248
  initializeParallelMatrix,