| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| (function (root) { |
| "use strict"; |
|
|
| |
| |
| |
| |
| |
| const BYTES = { F64: 8, F32: 4, F16: 2, BF16: 2, I64: 8, I32: 4, I16: 2, I8: 1, U8: 1, BOOL: 1 }; |
|
|
| |
| function bf16ToF32(u16, out) { |
| const u32 = new Uint32Array(out.buffer, out.byteOffset, out.length); |
| for (let i = 0; i < u16.length; i++) u32[i] = u16[i] << 16; |
| return out; |
| } |
| |
| |
| |
| function f16ToF32(u16, out) { |
| for (let i = 0; i < u16.length; i++) { |
| const h = u16[i], s = (h & 0x8000) >> 15, e = (h & 0x7C00) >> 10, f = h & 0x03FF; |
| let v; |
| if (e === 0) v = f === 0 ? 0 : Math.pow(2, -14) * (f / 1024); |
| else if (e === 0x1F) v = f === 0 ? Infinity : NaN; |
| else v = Math.pow(2, e - 15) * (1 + f / 1024); |
| out[i] = s ? -v : v; |
| } |
| return out; |
| } |
|
|
| function toF32(dtype, bytes) { |
| const n = bytes.byteLength / BYTES[dtype]; |
| if (dtype === "F32") { |
| |
| const out = new Float32Array(n); |
| new Uint8Array(out.buffer).set(bytes); |
| return out; |
| } |
| const out = new Float32Array(n); |
| if (dtype === "F16" || dtype === "BF16") { |
| const u16 = new Uint16Array(n); |
| new Uint8Array(u16.buffer).set(bytes); |
| return dtype === "BF16" ? bf16ToF32(u16, out) : f16ToF32(u16, out); |
| } |
| if (dtype === "F64") { |
| const f64 = new Float64Array(n); |
| new Uint8Array(f64.buffer).set(bytes); |
| for (let i = 0; i < n; i++) out[i] = f64[i]; |
| return out; |
| } |
| throw new Error(`unsupported tensor dtype ${dtype} — weights must be F32, F16, BF16 or F64`); |
| } |
|
|
| |
| |
| |
| |
| |
| function parseHeader(buf) { |
| const dv = new DataView(buf); |
| const lo = dv.getUint32(0, true), hi = dv.getUint32(4, true); |
| if (hi !== 0) throw new Error("safetensors header length is implausibly large — not a safetensors file"); |
| if (lo <= 0 || lo > buf.byteLength - 8) throw new Error("safetensors header is truncated"); |
| let json; |
| try { json = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 8, lo))); } |
| catch (e) { throw new Error("safetensors header is not valid JSON — not a safetensors file"); } |
| const dataStart = 8 + lo; |
| const tensors = new Map(); |
| for (const [name, t] of Object.entries(json)) { |
| if (name === "__metadata__") continue; |
| if (!t || !t.dtype || !Array.isArray(t.shape) || !Array.isArray(t.data_offsets)) |
| throw new Error(`safetensors header entry "${name}" is malformed`); |
| const [s, e] = t.data_offsets; |
| const elems = t.shape.reduce((a, b) => a * b, 1); |
| const want = elems * (BYTES[t.dtype] || 0); |
| if (BYTES[t.dtype] && e - s !== want) |
| throw new Error(`tensor "${name}": header claims ${e - s} bytes for a ${t.shape.join("x")} ${t.dtype} (expected ${want})`); |
| tensors.set(name, { name, dtype: t.dtype, shape: t.shape, elems, |
| start: dataStart + s, end: dataStart + e, bytes: e - s }); |
| } |
| return { tensors, dataStart, metadata: json.__metadata__ || {} }; |
| } |
|
|
| |
| |
| |
| function f32Bytes(tensors) { |
| let n = 0; |
| for (const t of tensors) n += t.elems * 4; |
| return n; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function coalesce(tensors, slack) { |
| const gap = slack == null ? 1 << 20 : slack; |
| const sorted = [...tensors].sort((a, b) => a.start - b.start); |
| const runs = []; |
| for (const t of sorted) { |
| const last = runs[runs.length - 1]; |
| if (last && t.start - last.end <= gap) { last.end = Math.max(last.end, t.end); last.tensors.push(t); } |
| else runs.push({ start: t.start, end: t.end, tensors: [t] }); |
| } |
| return runs; |
| } |
|
|
| const api = { BYTES, parseHeader, toF32, coalesce, f32Bytes, f16ToF32, bf16ToF32 }; |
| if (typeof module !== "undefined" && module.exports) module.exports = api; |
| else root.Safetensors = api; |
| })(typeof self !== "undefined" ? self : this); |
|
|