wifi-densepose/vendor/ruvector/npm/packages/ruvllm-wasm/src/index.ts

277 lines
6.8 KiB
TypeScript

/**
* @ruvector/ruvllm-wasm - Browser LLM Inference with WebAssembly
*
* Run large language models directly in the browser using WebAssembly
* with optional WebGPU acceleration for faster inference.
*
* @example
* ```typescript
* import { RuvLLMWasm } from '@ruvector/ruvllm-wasm';
*
* // Initialize with WebGPU (if available)
* const llm = await RuvLLMWasm.create({ useWebGPU: true });
*
* // Load a model
* await llm.loadModel('https://example.com/model.gguf', {
* onProgress: (loaded, total) => console.log(`${loaded}/${total}`)
* });
*
* // Generate text
* const result = await llm.generate('Hello, world!', {
* maxTokens: 100,
* temperature: 0.7,
* });
*
* console.log(result.text);
* ```
*
* @packageDocumentation
*/
export {
WebGPUStatus,
LoadingStatus,
ModelArchitecture,
ModelMetadata,
WASMConfig,
GenerationConfig,
TokenCallback,
ProgressCallback,
InferenceStats,
ChatMessage,
CompletionResult,
DownloadProgress,
} from './types.js';
/** Package version */
export const VERSION = '0.1.0';
/**
* Check WebGPU availability
*/
export async function checkWebGPU(): Promise<import('./types.js').WebGPUStatus> {
if (typeof navigator === 'undefined') {
return 'not_supported' as import('./types.js').WebGPUStatus;
}
if (!('gpu' in navigator)) {
return 'not_supported' as import('./types.js').WebGPUStatus;
}
try {
const adapter = await (navigator as any).gpu.requestAdapter();
if (adapter) {
return 'available' as import('./types.js').WebGPUStatus;
}
return 'unavailable' as import('./types.js').WebGPUStatus;
} catch {
return 'unavailable' as import('./types.js').WebGPUStatus;
}
}
/**
* Check SharedArrayBuffer support (required for threading)
*/
export function checkSharedArrayBuffer(): boolean {
return typeof SharedArrayBuffer !== 'undefined';
}
/**
* Check SIMD support
*/
export async function checkSIMD(): Promise<boolean> {
try {
// Check for WASM SIMD support
const simdTest = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, 0x03,
0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00,
0x41, 0x00, 0xfd, 0x0f, 0x00, 0x0b,
]);
await WebAssembly.compile(simdTest);
return true;
} catch {
return false;
}
}
/**
* Get browser capabilities for LLM inference
*/
export async function getCapabilities(): Promise<{
webgpu: import('./types.js').WebGPUStatus;
sharedArrayBuffer: boolean;
simd: boolean;
crossOriginIsolated: boolean;
}> {
const [webgpu, simd] = await Promise.all([
checkWebGPU(),
checkSIMD(),
]);
return {
webgpu,
sharedArrayBuffer: checkSharedArrayBuffer(),
simd,
crossOriginIsolated: typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated,
};
}
/**
* Format file size for display
*/
export function formatFileSize(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
/**
* Estimate memory requirements for a model
*/
export function estimateMemory(fileSizeBytes: number): {
minimum: number;
recommended: number;
} {
// Rough estimates based on model size
const fileSizeMB = fileSizeBytes / (1024 * 1024);
return {
minimum: Math.ceil(fileSizeMB * 1.2), // 20% overhead
recommended: Math.ceil(fileSizeMB * 1.5), // 50% overhead for KV cache
};
}
/**
* RuvLLM WASM class placeholder
* Full implementation requires WASM binary from ruvllm-wasm crate
*/
export class RuvLLMWasm {
private config: import('./types.js').WASMConfig;
private status: import('./types.js').LoadingStatus = 'idle' as import('./types.js').LoadingStatus;
private constructor(config: import('./types.js').WASMConfig) {
this.config = config;
}
/**
* Create a new RuvLLMWasm instance
*/
static async create(options?: {
useWebGPU?: boolean;
threads?: number;
memoryLimit?: number;
}): Promise<RuvLLMWasm> {
const config: import('./types.js').WASMConfig = {
threads: options?.threads,
memoryLimit: options?.memoryLimit,
simd: await checkSIMD(),
cacheModels: true,
};
if (options?.useWebGPU) {
const webgpuStatus = await checkWebGPU();
if (webgpuStatus === 'available') {
const adapter = await (navigator as any).gpu.requestAdapter();
if (adapter) {
config.device = await adapter.requestDevice();
}
}
}
return new RuvLLMWasm(config);
}
/**
* Get current loading status
*/
getStatus(): import('./types.js').LoadingStatus {
return this.status;
}
/**
* Load a model from URL or ArrayBuffer
*/
async loadModel(
source: string | ArrayBuffer,
options?: {
onProgress?: import('./types.js').ProgressCallback;
}
): Promise<import('./types.js').ModelMetadata> {
this.status = 'loading' as import('./types.js').LoadingStatus;
// Placeholder - actual implementation requires WASM binary
console.log('Loading model from:', typeof source === 'string' ? source : 'ArrayBuffer');
console.log('Note: Full model loading requires the ruvllm-wasm binary.');
console.log('Build from: crates/ruvllm-wasm');
this.status = 'ready' as import('./types.js').LoadingStatus;
return {
name: 'placeholder',
architecture: 'llama' as import('./types.js').ModelArchitecture,
parameters: '0B',
contextLength: 2048,
vocabSize: 32000,
embeddingDim: 2048,
numLayers: 22,
quantization: 'q4_k_m',
fileSize: 0,
};
}
/**
* Generate text completion
*/
async generate(
prompt: string,
config?: import('./types.js').GenerationConfig,
onToken?: import('./types.js').TokenCallback
): Promise<import('./types.js').CompletionResult> {
console.log('Generating with prompt:', prompt.substring(0, 50) + '...');
console.log('Note: Full generation requires the ruvllm-wasm binary.');
return {
text: '[Placeholder - build ruvllm-wasm crate for actual inference]',
stats: {
tokensGenerated: 0,
timeToFirstToken: 0,
totalTime: 0,
tokensPerSecond: 0,
promptTokens: 0,
memoryUsed: 0,
},
finishReason: 'stop',
};
}
/**
* Chat completion with message history
*/
async chat(
messages: import('./types.js').ChatMessage[],
config?: import('./types.js').GenerationConfig,
onToken?: import('./types.js').TokenCallback
): Promise<import('./types.js').CompletionResult> {
const prompt = messages
.map(m => `${m.role}: ${m.content}`)
.join('\n');
return this.generate(prompt, config, onToken);
}
/**
* Unload model and free memory
*/
unload(): void {
this.status = 'idle' as import('./types.js').LoadingStatus;
}
}