@danielsimonjr/mathts-matrix 0.2.1 → 0.3.0
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.
- package/dist/index.d.ts +390 -714
- package/dist/index.js +1740 -2359
- package/package.json +7 -6
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/
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
3132
|
+
output[wid.x] = sdata[0];
|
|
3683
3133
|
}
|
|
3684
3134
|
}
|
|
3685
3135
|
`
|
|
3686
3136
|
};
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
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
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
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
|
|
3160
|
+
* Get the current status
|
|
3748
3161
|
*/
|
|
3749
|
-
|
|
3750
|
-
|
|
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
|
-
*
|
|
3166
|
+
* Check if backend is ready
|
|
3756
3167
|
*/
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
this.getBuiltinShader(name);
|
|
3760
|
-
this.getBuiltinPipeline(name);
|
|
3761
|
-
}
|
|
3168
|
+
get isReady() {
|
|
3169
|
+
return this._status === "ready";
|
|
3762
3170
|
}
|
|
3763
3171
|
/**
|
|
3764
|
-
*
|
|
3172
|
+
* Get capabilities
|
|
3765
3173
|
*/
|
|
3766
|
-
|
|
3767
|
-
this.
|
|
3174
|
+
get capabilities() {
|
|
3175
|
+
return this._capabilities;
|
|
3768
3176
|
}
|
|
3769
3177
|
/**
|
|
3770
|
-
* Get
|
|
3178
|
+
* Get last error
|
|
3771
3179
|
*/
|
|
3772
|
-
|
|
3773
|
-
|
|
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
|
-
*
|
|
3184
|
+
* Initialize the GPU backend
|
|
3792
3185
|
*/
|
|
3793
|
-
|
|
3794
|
-
this.
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
this.
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
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
|
-
*
|
|
3231
|
+
* Check if GPU should be used for the given matrix size
|
|
3805
3232
|
*/
|
|
3806
|
-
|
|
3807
|
-
|
|
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
|
-
*
|
|
3240
|
+
* Calculate workgroup counts for a matrix
|
|
3811
3241
|
*/
|
|
3812
|
-
|
|
3813
|
-
|
|
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
|
-
*
|
|
3247
|
+
* Get the GPU context
|
|
3817
3248
|
*/
|
|
3818
|
-
|
|
3819
|
-
|
|
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
|
-
*
|
|
3256
|
+
* Get the buffer pool
|
|
3823
3257
|
*/
|
|
3824
|
-
|
|
3825
|
-
this.
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
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
|
-
*
|
|
3265
|
+
* Get the shader manager
|
|
3835
3266
|
*/
|
|
3836
|
-
|
|
3837
|
-
this.
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
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
|
-
*
|
|
3274
|
+
* Add two matrices element-wise
|
|
3847
3275
|
*/
|
|
3848
|
-
|
|
3849
|
-
this.
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
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
|
-
*
|
|
3304
|
+
* Multiply two matrices
|
|
3859
3305
|
*/
|
|
3860
|
-
|
|
3861
|
-
this.
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
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
|
-
*
|
|
3336
|
+
* Transpose a matrix
|
|
3871
3337
|
*/
|
|
3872
|
-
|
|
3873
|
-
this.
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
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
|
-
*
|
|
3362
|
+
* Scale a matrix by a scalar
|
|
3883
3363
|
*/
|
|
3884
|
-
|
|
3885
|
-
this.
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
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
|
-
*
|
|
3389
|
+
* Get backend statistics
|
|
3894
3390
|
*/
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
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
|
-
*
|
|
3400
|
+
* Destroy the backend
|
|
3905
3401
|
*/
|
|
3906
|
-
|
|
3907
|
-
this.
|
|
3908
|
-
|
|
3909
|
-
this.
|
|
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
|
-
*
|
|
3456
|
+
* Check if GPU is available in the current environment
|
|
3914
3457
|
*/
|
|
3915
|
-
|
|
3916
|
-
if (this.
|
|
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
|
-
*
|
|
3466
|
+
* Initialize the GPU backend
|
|
3952
3467
|
*/
|
|
3953
|
-
|
|
3954
|
-
if (this.
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
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
|
-
*
|
|
3499
|
+
* Check if operation should use GPU
|
|
3968
3500
|
*/
|
|
3969
|
-
|
|
3970
|
-
|
|
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
|
-
*
|
|
3505
|
+
* Execute GPU operation with fallback
|
|
3998
3506
|
*/
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
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
|
-
*
|
|
3519
|
+
* Get GPU capabilities
|
|
4016
3520
|
*/
|
|
4017
|
-
|
|
4018
|
-
|
|
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
|
-
*
|
|
3525
|
+
* Get backend statistics
|
|
4035
3526
|
*/
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
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
|
|
3538
|
+
return jsBackend.add(a, b);
|
|
4044
3539
|
}
|
|
4045
3540
|
/**
|
|
4046
|
-
*
|
|
3541
|
+
* Async add operation using GPU
|
|
4047
3542
|
*/
|
|
4048
|
-
|
|
4049
|
-
|
|
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
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
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
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
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
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
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
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
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
|
-
*
|
|
3587
|
+
* Async scale operation using GPU
|
|
4129
3588
|
*/
|
|
4130
|
-
async
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
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
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
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
|
-
*
|
|
3620
|
+
* Async matrix multiplication using GPU
|
|
4186
3621
|
*/
|
|
4187
|
-
async
|
|
4188
|
-
const
|
|
4189
|
-
|
|
4190
|
-
|
|
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
|
|
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
|
-
*
|
|
3645
|
+
* Async transpose using GPU
|
|
4208
3646
|
*/
|
|
4209
|
-
async
|
|
4210
|
-
const
|
|
4211
|
-
|
|
4212
|
-
|
|
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
|
|
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
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
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
|
-
*
|
|
3680
|
+
* Update configuration
|
|
4270
3681
|
*/
|
|
4271
|
-
|
|
4272
|
-
|
|
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
|
|
3686
|
+
* Get current configuration
|
|
4278
3687
|
*/
|
|
4279
|
-
|
|
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
|
|
3692
|
+
* Destroy the backend
|
|
4292
3693
|
*/
|
|
4293
3694
|
destroy() {
|
|
4294
|
-
|
|
4295
|
-
|
|
3695
|
+
if (this.backend && !this.config.useGlobalBackend) {
|
|
3696
|
+
this.backend.destroy();
|
|
4296
3697
|
}
|
|
4297
|
-
this.
|
|
4298
|
-
this.pendingTransfers.clear();
|
|
3698
|
+
this.backend = null;
|
|
4299
3699
|
}
|
|
4300
3700
|
};
|
|
4301
|
-
|
|
4302
|
-
|
|
3701
|
+
var gpuMatrixBackend = new GPUMatrixBackend();
|
|
3702
|
+
function createGPUMatrixBackend(config) {
|
|
3703
|
+
return new GPUMatrixBackend(config);
|
|
4303
3704
|
}
|
|
4304
3705
|
|
|
4305
|
-
// src/
|
|
4306
|
-
var
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
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
|
-
*
|
|
3803
|
+
* Sync manager state with global config
|
|
4323
3804
|
*/
|
|
4324
|
-
|
|
4325
|
-
|
|
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
|
-
*
|
|
3821
|
+
* Initialize all available backends
|
|
4329
3822
|
*/
|
|
4330
|
-
|
|
4331
|
-
|
|
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
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
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
|
-
*
|
|
3844
|
+
* Update backend hints
|
|
4341
3845
|
*/
|
|
4342
|
-
|
|
4343
|
-
|
|
3846
|
+
setHints(hints) {
|
|
3847
|
+
this.hints = { ...this.hints, ...hints };
|
|
3848
|
+
backendRegistry.setHints(hints);
|
|
4344
3849
|
}
|
|
4345
3850
|
/**
|
|
4346
|
-
*
|
|
3851
|
+
* Get current hints
|
|
4347
3852
|
*/
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
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
|
-
|
|
4353
|
-
|
|
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
|
-
|
|
4356
|
-
|
|
4357
|
-
if (
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
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
|
-
*
|
|
3891
|
+
* Execute an operation with automatic backend selection
|
|
4393
3892
|
*/
|
|
4394
|
-
|
|
4395
|
-
if (!this.
|
|
4396
|
-
return
|
|
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
|
-
*
|
|
3908
|
+
* Matrix addition with auto backend selection
|
|
4402
3909
|
*/
|
|
4403
|
-
|
|
4404
|
-
const
|
|
4405
|
-
return
|
|
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
|
-
*
|
|
3918
|
+
* Matrix subtraction with auto backend selection
|
|
4409
3919
|
*/
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
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
|
-
*
|
|
3928
|
+
* Element-wise multiplication with auto backend selection
|
|
4418
3929
|
*/
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
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
|
-
*
|
|
3938
|
+
* Element-wise division with auto backend selection
|
|
4427
3939
|
*/
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
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
|
-
*
|
|
3948
|
+
* Scalar multiplication with auto backend selection
|
|
4436
3949
|
*/
|
|
4437
|
-
|
|
4438
|
-
const
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
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
|
-
*
|
|
3958
|
+
* Element-wise absolute value with auto backend selection
|
|
4466
3959
|
*/
|
|
4467
|
-
|
|
4468
|
-
const
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
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
|
-
*
|
|
3968
|
+
* Element-wise negation with auto backend selection
|
|
4498
3969
|
*/
|
|
4499
|
-
|
|
4500
|
-
const
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
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
|
-
*
|
|
3981
|
+
* Matrix multiplication with auto backend selection
|
|
4524
3982
|
*/
|
|
4525
|
-
|
|
4526
|
-
const
|
|
4527
|
-
const
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
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
|
-
*
|
|
3992
|
+
* Matrix transpose with auto backend selection
|
|
4551
3993
|
*/
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
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
|
-
*
|
|
4005
|
+
* Sum of all elements with auto backend selection
|
|
4562
4006
|
*/
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
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
|
-
*
|
|
4013
|
+
* Sum along axis with auto backend selection
|
|
4617
4014
|
*/
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
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
|
-
*
|
|
4023
|
+
* Frobenius norm with auto backend selection
|
|
4627
4024
|
*/
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
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
|
-
*
|
|
4033
|
+
* Dot product with auto backend selection
|
|
4660
4034
|
*/
|
|
4661
|
-
|
|
4662
|
-
|
|
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
|
-
*
|
|
4044
|
+
* Get list of available backends
|
|
4666
4045
|
*/
|
|
4667
|
-
|
|
4668
|
-
|
|
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
|
-
*
|
|
4050
|
+
* Check if a specific backend is available
|
|
4680
4051
|
*/
|
|
4681
|
-
|
|
4682
|
-
return
|
|
4052
|
+
hasBackend(type) {
|
|
4053
|
+
return backendRegistry.has(type);
|
|
4683
4054
|
}
|
|
4684
4055
|
/**
|
|
4685
|
-
* Get backend
|
|
4056
|
+
* Get current active backend for a given operation size
|
|
4686
4057
|
*/
|
|
4687
|
-
|
|
4688
|
-
return this.
|
|
4058
|
+
getActiveBackend(elementCount, operation) {
|
|
4059
|
+
return this.selectBackend(elementCount, operation).type;
|
|
4689
4060
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
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
|
-
*
|
|
4075
|
+
* Record a performance sample for adaptive tuning
|
|
4702
4076
|
*/
|
|
4703
|
-
|
|
4704
|
-
const
|
|
4705
|
-
if (!
|
|
4706
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
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
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
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
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
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
|
-
|
|
4154
|
+
this.adaptiveState.lastAdjustment = now;
|
|
4745
4155
|
}
|
|
4746
4156
|
/**
|
|
4747
|
-
*
|
|
4157
|
+
* Get current adaptive thresholds
|
|
4748
4158
|
*/
|
|
4749
|
-
|
|
4750
|
-
|
|
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
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
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
|
-
*
|
|
4173
|
+
* Get performance statistics
|
|
4781
4174
|
*/
|
|
4782
|
-
|
|
4783
|
-
const
|
|
4784
|
-
|
|
4785
|
-
|
|
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
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
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
|
|
4200
|
+
return {
|
|
4201
|
+
sampleCount: this.adaptiveState.samples.length,
|
|
4202
|
+
operationStats
|
|
4203
|
+
};
|
|
4803
4204
|
}
|
|
4804
4205
|
/**
|
|
4805
|
-
*
|
|
4206
|
+
* Cleanup resources
|
|
4806
4207
|
*/
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
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
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
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
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
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
|
-
*
|
|
4247
|
+
* Load the WASM module
|
|
4847
4248
|
*/
|
|
4848
|
-
|
|
4849
|
-
|
|
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
|
-
*
|
|
4261
|
+
* Precompile the WASM module without instantiation
|
|
4262
|
+
* Useful for build-time or startup optimization
|
|
4853
4263
|
*/
|
|
4854
|
-
|
|
4855
|
-
if (this.
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
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
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
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
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
5020
|
-
*
|
|
5021
|
-
*
|
|
5022
|
-
*
|
|
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
|
-
|
|
5026
|
-
const
|
|
5027
|
-
if (
|
|
5028
|
-
const
|
|
5029
|
-
|
|
5030
|
-
|
|
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
|
-
|
|
4335
|
+
const { defaultWasmLocation } = await import("./resolve-VJX3YTS6.js");
|
|
4336
|
+
return defaultWasmLocation(import.meta.url, wasmFile, { browser: true });
|
|
5049
4337
|
}
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
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
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
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
|
-
*
|
|
4414
|
+
* Get the loaded WASM module
|
|
5079
4415
|
*/
|
|
5080
|
-
|
|
5081
|
-
|
|
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
|
-
*
|
|
4420
|
+
* Get the compiled WASM module (for caching/serialization)
|
|
5089
4421
|
*/
|
|
5090
|
-
|
|
5091
|
-
|
|
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
|
-
*
|
|
4426
|
+
* Check if WASM is loaded
|
|
5099
4427
|
*/
|
|
5100
|
-
|
|
5101
|
-
|
|
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
|
-
*
|
|
4432
|
+
* Check if WASM is precompiled
|
|
5109
4433
|
*/
|
|
5110
|
-
|
|
5111
|
-
|
|
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
|
-
*
|
|
4438
|
+
* Get loading performance metrics
|
|
5119
4439
|
*/
|
|
5120
|
-
|
|
5121
|
-
|
|
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
|
-
*
|
|
4450
|
+
* Allocate Float64Array in WASM memory and copy `data` into it.
|
|
4451
|
+
* Uses memory pooling.
|
|
5129
4452
|
*/
|
|
5130
|
-
|
|
5131
|
-
const
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5144
|
-
const
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
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
|
-
*
|
|
4483
|
+
* Allocate Float64Array without copying data (for output buffers)
|
|
5153
4484
|
*/
|
|
5154
|
-
|
|
5155
|
-
const
|
|
5156
|
-
|
|
5157
|
-
|
|
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
|
-
*
|
|
4491
|
+
* Allocate Int32Array in WASM memory and copy `data` into it.
|
|
5166
4492
|
*/
|
|
5167
|
-
|
|
5168
|
-
const
|
|
5169
|
-
|
|
5170
|
-
|
|
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
|
-
*
|
|
4502
|
+
* Allocate Int32Array without copying data (for output buffers)
|
|
5174
4503
|
*/
|
|
5175
|
-
|
|
5176
|
-
const
|
|
5177
|
-
|
|
5178
|
-
|
|
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
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
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
|
-
*
|
|
4567
|
+
* Find a pool entry whose data region is large enough; returns null when
|
|
4568
|
+
* no suitable entry is available.
|
|
5194
4569
|
*/
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5207
|
-
|
|
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
|
-
*
|
|
4603
|
+
* Free allocated memory (immediate, bypasses pool): unpin the header pointer.
|
|
5211
4604
|
*/
|
|
5212
|
-
|
|
5213
|
-
|
|
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
|
-
*
|
|
4615
|
+
* Clear the memory pool
|
|
5217
4616
|
*/
|
|
5218
|
-
|
|
5219
|
-
|
|
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
|
-
*
|
|
4632
|
+
* Get pool statistics
|
|
5223
4633
|
*/
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
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
|
-
*
|
|
4653
|
+
* Run garbage collection.
|
|
5236
4654
|
*/
|
|
5237
|
-
|
|
5238
|
-
const
|
|
5239
|
-
if (!
|
|
5240
|
-
|
|
5241
|
-
|
|
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
|
-
*
|
|
4663
|
+
* Reset the loader (for testing)
|
|
5255
4664
|
*/
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
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
|
-
*
|
|
4702
|
+
* Create a new batch executor
|
|
5318
4703
|
*/
|
|
5319
|
-
|
|
5320
|
-
|
|
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
|
-
*
|
|
4715
|
+
* Get current batch size
|
|
5324
4716
|
*/
|
|
5325
|
-
|
|
5326
|
-
this.
|
|
5327
|
-
samples: [],
|
|
5328
|
-
lastAdjustment: 0,
|
|
5329
|
-
adjustedThresholds: /* @__PURE__ */ new Map()
|
|
5330
|
-
};
|
|
4717
|
+
get size() {
|
|
4718
|
+
return this.operations.length;
|
|
5331
4719
|
}
|
|
5332
4720
|
/**
|
|
5333
|
-
*
|
|
4721
|
+
* Check if batch is empty
|
|
5334
4722
|
*/
|
|
5335
|
-
|
|
5336
|
-
|
|
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
|
-
*
|
|
4727
|
+
* Check if batch is full
|
|
5367
4728
|
*/
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
this.configUnsubscribe();
|
|
5371
|
-
this.configUnsubscribe = null;
|
|
5372
|
-
}
|
|
4729
|
+
get isFull() {
|
|
4730
|
+
return this.operations.length >= this.options.maxBatchSize;
|
|
5373
4731
|
}
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
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
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
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
|
-
*
|
|
4757
|
+
* Queue an element-wise multiply operation
|
|
5408
4758
|
*/
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
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
|
-
*
|
|
5422
|
-
* Useful for build-time or startup optimization
|
|
4769
|
+
* Queue a scale operation
|
|
5423
4770
|
*/
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
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
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
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
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
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
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
this.
|
|
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
|
-
*
|
|
4824
|
+
* Flush all queued operations (async)
|
|
5575
4825
|
*/
|
|
5576
|
-
|
|
5577
|
-
|
|
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
|
-
*
|
|
4862
|
+
* Flush synchronously (fire and forget)
|
|
5581
4863
|
*/
|
|
5582
|
-
|
|
5583
|
-
|
|
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
|
-
*
|
|
4878
|
+
* Encode a single operation into the command encoder
|
|
5587
4879
|
*/
|
|
5588
|
-
|
|
5589
|
-
|
|
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
|
-
*
|
|
4908
|
+
* Get pipeline name for operation type
|
|
5593
4909
|
*/
|
|
5594
|
-
|
|
5595
|
-
|
|
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
|
-
*
|
|
4926
|
+
* Create params buffer for operation
|
|
5599
4927
|
*/
|
|
5600
|
-
|
|
5601
|
-
|
|
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
|
-
*
|
|
5611
|
-
* Uses memory pooling.
|
|
4945
|
+
* Calculate workgroup dispatch counts
|
|
5612
4946
|
*/
|
|
5613
|
-
|
|
5614
|
-
const
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5633
|
-
|
|
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
|
-
*
|
|
4963
|
+
* Get statistics about the batch executor
|
|
5644
4964
|
*/
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
4965
|
+
getStats() {
|
|
4966
|
+
return {
|
|
4967
|
+
queuedOperations: this.operations.length,
|
|
4968
|
+
maxBatchSize: this.options.maxBatchSize,
|
|
4969
|
+
autoFlush: this.options.autoFlush
|
|
4970
|
+
};
|
|
5649
4971
|
}
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
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
|
-
*
|
|
4997
|
+
* Upload data from CPU to GPU
|
|
5663
4998
|
*/
|
|
5664
|
-
|
|
5665
|
-
const
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
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
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
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
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
const
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
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
|
-
*
|
|
5728
|
-
* no suitable entry is available.
|
|
5039
|
+
* Download data using double-buffering for overlap
|
|
5729
5040
|
*/
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
5754
|
-
const
|
|
5755
|
-
const
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
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
|
-
|
|
5115
|
+
return result;
|
|
5761
5116
|
}
|
|
5762
5117
|
/**
|
|
5763
|
-
*
|
|
5118
|
+
* Batch multiple transfers
|
|
5764
5119
|
*/
|
|
5765
|
-
|
|
5766
|
-
const
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
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
|
-
*
|
|
5153
|
+
* Create or reuse a staging buffer
|
|
5776
5154
|
*/
|
|
5777
|
-
|
|
5778
|
-
const
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
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.
|
|
5789
|
-
this.
|
|
5162
|
+
const buffer = this.context.createStagingBuffer(alignedSize, "sync-staging");
|
|
5163
|
+
this.stagingBuffers.push(buffer);
|
|
5164
|
+
return buffer;
|
|
5790
5165
|
}
|
|
5791
5166
|
/**
|
|
5792
|
-
*
|
|
5167
|
+
* Round up to next power of 2
|
|
5793
5168
|
*/
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
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
|
-
*
|
|
5180
|
+
* Wait for all pending transfers to complete
|
|
5814
5181
|
*/
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
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
|
-
*
|
|
5188
|
+
* Get synchronization statistics
|
|
5824
5189
|
*/
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
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
|
-
|
|
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,
|