7254f7b4d1
Build / Build (macos-latest) (push) Has been cancelled
Build / Build (ubuntu-latest) (push) Has been cancelled
Build / Build (windows-latest) (push) Has been cancelled
Build / Analyze (javascript) (push) Has been cancelled
Build / Analyze (python) (push) Has been cancelled
240 lines
7.4 KiB
JavaScript
240 lines
7.4 KiB
JavaScript
|
|
import * as json from './json.js';
|
|
|
|
const safetensors = {};
|
|
|
|
safetensors.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const container = safetensors.Reader.open(context);
|
|
if (container) {
|
|
return context.set('safetensors', container);
|
|
}
|
|
const obj = await context.peek('json');
|
|
if (obj && obj.weight_map) {
|
|
const entries = Object.entries(obj.weight_map);
|
|
if (entries.length > 0 && entries.every(([, value]) => typeof value === 'string' && value.endsWith('.safetensors'))) {
|
|
return context.set('safetensors.json', entries);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async open(context) {
|
|
switch (context.type) {
|
|
case 'safetensors': {
|
|
const container = context.value;
|
|
await container.read();
|
|
return new safetensors.Model(container.entries);
|
|
}
|
|
case 'safetensors.json': {
|
|
const weight_map = new Map(context.value);
|
|
const keys = new Set(weight_map.keys());
|
|
const files = Array.from(new Set(weight_map.values()));
|
|
const contexts = await Promise.all(files.map((name) => context.fetch(name)));
|
|
const containers = contexts.map((context) => safetensors.Reader.open(context));
|
|
await Promise.all(containers.map((container) => container.read()));
|
|
const entries = new Map();
|
|
for (const container of containers) {
|
|
for (const [key, value] of Array.from(container.entries)) {
|
|
if (keys.has(key)) {
|
|
entries.set(key, value);
|
|
}
|
|
}
|
|
}
|
|
return new safetensors.Model(entries);
|
|
}
|
|
default: {
|
|
throw new safetensors.Error(`Unsupported Safetensors format '${context.type}'.`);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
safetensors.Model = class {
|
|
|
|
constructor(entries) {
|
|
this.format = 'Safetensors';
|
|
this.modules = [new safetensors.Module(entries)];
|
|
}
|
|
};
|
|
|
|
safetensors.Module = class {
|
|
|
|
constructor(entries) {
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const layers = new Map();
|
|
for (const [key, value] of Array.from(entries)) {
|
|
if (key === '__metadata__') {
|
|
continue;
|
|
}
|
|
const parts = key.split('.');
|
|
const name = parts.pop();
|
|
const layer = parts.join('.');
|
|
if (!layers.has(layer)) {
|
|
layers.set(layer, []);
|
|
}
|
|
layers.get(layer).push([name, key, value]);
|
|
}
|
|
for (const [name, values] of layers) {
|
|
const node = new safetensors.Node(name, values);
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
safetensors.Argument = class {
|
|
|
|
constructor(name, value) {
|
|
this.name = name;
|
|
this.value = value;
|
|
}
|
|
};
|
|
|
|
safetensors.Value = class {
|
|
|
|
constructor(name, value) {
|
|
this.name = name;
|
|
this.type = value.type;
|
|
this.initializer = value;
|
|
}
|
|
};
|
|
|
|
safetensors.Node = class {
|
|
|
|
constructor(name, values) {
|
|
this.name = name;
|
|
this.type = { name: 'Module' };
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
for (const [name, identifier, obj] of values) {
|
|
const tensor = new safetensors.Tensor(obj);
|
|
const value = new safetensors.Value(identifier, tensor);
|
|
const argument = new safetensors.Argument(name, [value]);
|
|
this.inputs.push(argument);
|
|
}
|
|
}
|
|
};
|
|
|
|
safetensors.TensorType = class {
|
|
|
|
constructor(dtype, shape) {
|
|
switch (dtype) {
|
|
case 'I8': this.dataType = 'int8'; break;
|
|
case 'I16': this.dataType = 'int16'; break;
|
|
case 'I32': this.dataType = 'int32'; break;
|
|
case 'I64': this.dataType = 'int64'; break;
|
|
case 'U8': this.dataType = 'uint8'; break;
|
|
case 'U16': this.dataType = 'uint16'; break;
|
|
case 'U32': this.dataType = 'uint32'; break;
|
|
case 'U64': this.dataType = 'uint64'; break;
|
|
case 'BF16': this.dataType = 'bfloat16'; break;
|
|
case 'F16': this.dataType = 'float16'; break;
|
|
case 'F32': this.dataType = 'float32'; break;
|
|
case 'F64': this.dataType = 'float64'; break;
|
|
case 'BOOL': this.dataType = 'boolean'; break;
|
|
case 'F8_E4M3': this.dataType = 'float8e4m3fn'; break;
|
|
case 'F8_E5M2': this.dataType = 'float8e5m2'; break;
|
|
case 'F8_E8M0': this.dataType = 'float8e8m0'; break;
|
|
default: throw new safetensors.Error(`Unsupported data type '${dtype}'.`);
|
|
}
|
|
this.shape = shape;
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
safetensors.TensorShape = class {
|
|
|
|
constructor(dimensions) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
};
|
|
|
|
safetensors.Tensor = class {
|
|
|
|
constructor(obj) {
|
|
const shape = new safetensors.TensorShape(obj.shape);
|
|
this.type = new safetensors.TensorType(obj.dtype, shape);
|
|
this.encoding = '<';
|
|
this.data = obj.__data__;
|
|
}
|
|
|
|
get values() {
|
|
if (this.data instanceof Uint8Array) {
|
|
return this.data;
|
|
}
|
|
if (this.data && this.data.peek) {
|
|
return this.data.peek();
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
safetensors.Reader = class {
|
|
|
|
static open(context) {
|
|
const identifier = context.identifier;
|
|
const stream = context.stream;
|
|
if (stream.length > 9) {
|
|
const buffer = stream.peek(9);
|
|
// Safetensors implementation caps headers length at 100 MB.
|
|
if (buffer[4] === 0 && buffer[5] === 0 && buffer[6] === 0 && buffer[7] === 0 && buffer[8] === 0x7b) {
|
|
const size = (buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24) >>> 0;
|
|
if (size < stream.length) {
|
|
return new safetensors.Reader(identifier, stream, size);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(identifier, stream, size) {
|
|
this.identifier = identifier;
|
|
this.size = size;
|
|
this.stream = stream;
|
|
this.entries = new Map();
|
|
}
|
|
|
|
async read() {
|
|
const stream = this.stream;
|
|
const position = stream.position;
|
|
stream.seek(8);
|
|
const buffer = stream.read(this.size);
|
|
const reader = json.TextReader.open(buffer);
|
|
const obj = reader.read();
|
|
const offset = stream.position;
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
if (key === '__metadata__') {
|
|
continue;
|
|
}
|
|
const [start, end] = value.data_offsets;
|
|
stream.seek(offset + start);
|
|
value.__data__ = stream.stream(end - start);
|
|
this.entries.set(key, value);
|
|
}
|
|
stream.seek(position);
|
|
delete this.size;
|
|
delete this.stream;
|
|
}
|
|
};
|
|
|
|
safetensors.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading Safetensors model.';
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = safetensors.ModelFactory;
|