Download public/safetensors.js from DaisyChainAI/DaisyChain-Infer: direct link, hf CLI and curl.
- Browser
- Download file 6.13 kB
-
https://huggingface.co/DaisyChainAI/DaisyChain-Infer/resolve/30bafb7c7526b8606df2b681f1b2def21c83d5cf/public/safetensors.js
- Command line
-
hf download hf://DaisyChainAI/DaisyChain-Infer@30bafb7c7526b8606df2b681f1b2def21c83d5cf/public/safetensors.js
-
curl -L -o safetensors.js https://huggingface.co/DaisyChainAI/DaisyChain-Infer/resolve/30bafb7c7526b8606df2b681f1b2def21c83d5cf/public/safetensors.js
6.13 kB
| // safetensors, read a piece at a time. | |
| // | |
| // The format is what makes this project's memory pooling honest. A file is: | |
| // | |
| // [u64 headerLen][JSON header][ tensor bytes, back to back ] | |
| // | |
| // and the header gives every tensor's exact byte range. So a device does not | |
| // need the model — it needs its own layers, and it can ask the CDN for exactly | |
| // those bytes. Nothing else is ever downloaded, decoded, or held. | |
| // | |
| // That is the difference between "we split the model across devices" and | |
| // "one device downloads the model and hands out pieces". Only the first one | |
| // actually lets a group run something none of them could hold. | |
| (function (root) { | |
| "use strict"; | |
| // ---- dtype decoding --------------------------------------------------------- | |
| // Everything becomes f32, because that is what the verified units quantize | |
| // from. F16 and BF16 are the common storage formats and both widen exactly — | |
| // no rounding, no device-dependent behaviour, so a CPU device and a GPU | |
| // device still agree bit-for-bit after loading. | |
| const BYTES = { F64: 8, F32: 4, F16: 2, BF16: 2, I64: 8, I32: 4, I16: 2, I8: 1, U8: 1, BOOL: 1 }; | |
| // BF16 is the top 16 bits of an f32 — widening is a shift, exactly. | |
| 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; | |
| } | |
| // F16 -> F32 by hand rather than via Float16Array, which is not everywhere | |
| // yet. Subnormals and inf/nan are handled explicitly; every f16 has an exact | |
| // f32 representation, so this is a widening, not a conversion. | |
| 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") { | |
| // may be unaligned inside a range response — copy rather than view | |
| 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`); | |
| } | |
| // ---- header ---------------------------------------------------------------- | |
| // The first 8 bytes are a u64 length. It is read as two u32s because a JS | |
| // number cannot hold a u64 — but a header that genuinely needed the high word | |
| // would be gigabytes, so a nonzero high word means the file is not what it | |
| // claims and is refused rather than truncated into something plausible. | |
| 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__ || {} }; | |
| } | |
| // How much a set of tensors will cost to hold, before fetching any of it. | |
| // Reported in the UI so a device can see whether its slice fits BEFORE it | |
| // spends the bandwidth finding out. | |
| function f32Bytes(tensors) { | |
| let n = 0; | |
| for (const t of tensors) n += t.elems * 4; | |
| return n; | |
| } | |
| // ---- coalescing ------------------------------------------------------------- | |
| // A stage's tensors are contiguous in the file far more often than not | |
| // (safetensors writes them in the order they were registered, which follows | |
| // layer order). Merging adjacent ranges turns ~9 requests per layer into | |
| // roughly one per stage. Gaps below `slack` are downloaded and discarded | |
| // because one extra request costs more than a few unused kilobytes. | |
| 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); | |