| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| (function (root) { |
| "use strict"; |
|
|
| let ST; |
| const HUB = "https://huggingface.co"; |
|
|
| |
| |
| |
| function redact(s, token) { |
| if (!token) return s; |
| return String(s).split(token).join("hf_***"); |
| } |
|
|
| function headers(token, extra) { |
| const h = Object.assign({}, extra || {}); |
| if (token) h["Authorization"] = "Bearer " + token; |
| return h; |
| } |
|
|
| function fileUrl(repo, file, revision) { |
| return `${HUB}/${repo}/resolve/${encodeURIComponent(revision || "main")}/${file}`; |
| } |
|
|
| |
| |
| |
| |
| class HFError extends Error { |
| constructor(msg, kind, status) { super(msg); this.kind = kind; this.status = status; } |
| } |
| function statusError(res, repo, file, hasToken) { |
| const where = `${repo}/${file}`; |
| if (res.status === 401) |
| return new HFError(hasToken |
| ? `${where}: the token was rejected (401). Check it is a valid READ token and has not been revoked.` |
| : `${where} needs authentication (401).`, "auth", 401); |
| if (res.status === 403) |
| return new HFError(hasToken |
| ? `${where}: this token does not have access (403). If the model is gated, accept its licence on the model page first.` |
| : `${where} is gated or private (403).`, "auth", 403); |
| if (res.status === 404) |
| return new HFError(`${where} not found (404). Check the repo id, and that this model ships .safetensors weights.`, "missing", 404); |
| return new HFError(`${where}: HTTP ${res.status}`, "http", res.status); |
| } |
|
|
| async function getJSON(repo, file, revision, token) { |
| const res = await fetch(fileUrl(repo, file, revision), { headers: headers(token) }); |
| if (!res.ok) throw statusError(res, repo, file, !!token); |
| try { return await res.json(); } |
| catch (e) { throw new HFError(`${repo}/${file} is not valid JSON`, "parse", res.status); } |
| } |
|
|
| |
| |
| |
| |
| async function getRange(repo, file, revision, token, start, end) { |
| const res = await fetch(fileUrl(repo, file, revision), { |
| headers: headers(token, { Range: `bytes=${start}-${end - 1}` }), |
| }); |
| if (res.status === 200) |
| throw new HFError(`${repo}/${file}: the server ignored the byte range and returned the whole file — refusing it.`, "norange", 200); |
| if (!res.ok && res.status !== 206) throw statusError(res, repo, file, !!token); |
| return res.arrayBuffer(); |
| } |
|
|
| |
| |
| |
| |
| async function listWeightFiles(repo, revision, token) { |
| try { |
| const idx = await getJSON(repo, "model.safetensors.index.json", revision, token); |
| const files = [...new Set(Object.values(idx.weight_map || {}))]; |
| if (!files.length) throw new HFError("weight index lists no shards", "parse", 200); |
| return { files, weightMap: idx.weight_map }; |
| } catch (e) { |
| if (e.kind === "auth") throw e; |
| return { files: ["model.safetensors"], weightMap: null }; |
| } |
| } |
|
|
| |
| |
| |
| async function readIndex(repo, revision, token, onProgress) { |
| const { files } = await listWeightFiles(repo, revision, token); |
| const tensors = new Map(); |
| for (const f of files) { |
| if (onProgress) onProgress(`reading ${f} header…`); |
| const head = await getRange(repo, f, revision, token, 0, 8); |
| const dv = new DataView(head); |
| const lo = dv.getUint32(0, true), hi = dv.getUint32(4, true); |
| if (hi !== 0) throw new HFError(`${f}: implausible header length — not a safetensors file`, "parse", 200); |
| const full = await getRange(repo, f, revision, token, 0, 8 + lo); |
| const parsed = ST.parseHeader(full); |
| for (const [name, t] of parsed.tensors) tensors.set(name, Object.assign({ file: f }, t)); |
| } |
| return { files, tensors }; |
| } |
|
|
| |
| |
| async function fetchTensors(repo, revision, token, tensors, onProgress) { |
| const out = new Map(); |
| const byFile = new Map(); |
| for (const t of tensors) { |
| if (!byFile.has(t.file)) byFile.set(t.file, []); |
| byFile.get(t.file).push(t); |
| } |
| let done = 0; |
| const total = tensors.reduce((a, t) => a + t.bytes, 0); |
| for (const [file, list] of byFile) { |
| for (const run of ST.coalesce(list)) { |
| const buf = await getRange(repo, file, revision, token, run.start, run.end); |
| for (const t of run.tensors) { |
| const off = t.start - run.start; |
| out.set(t.name, ST.toF32(t.dtype, new Uint8Array(buf, off, t.bytes))); |
| done += t.bytes; |
| if (onProgress) onProgress(`${(done / 1048576).toFixed(1)} / ${(total / 1048576).toFixed(1)} MB`); |
| } |
| } |
| } |
| return out; |
| } |
|
|
| |
| |
| async function needsAuth(repo, revision) { |
| try { |
| const res = await fetch(fileUrl(repo, "config.json", revision), { method: "HEAD" }); |
| return res.status === 401 || res.status === 403; |
| } catch (e) { return false; } |
| } |
|
|
| const api = { HUB, HFError, fileUrl, getJSON, getRange, listWeightFiles, readIndex, |
| fetchTensors, needsAuth, redact }; |
| if (typeof module !== "undefined" && module.exports) { ST = require("./safetensors.js"); module.exports = api; } |
| else { ST = root.Safetensors; root.HF = api; } |
| })(typeof self !== "undefined" ? self : this); |
|
|