Spaces:
Running
Running
Download server/utils/onnxModelLoader.ts from Felladrin/MiniSearch: direct link, hf CLI and curl.
- Browser
- Download file 5.76 kB
-
https://huggingface.co/spaces/Felladrin/MiniSearch/resolve/main/server/utils/onnxModelLoader.ts
- Command line
-
hf download hf://spaces/Felladrin/MiniSearch/server/utils/onnxModelLoader.ts
-
curl -L -o onnxModelLoader.ts https://huggingface.co/spaces/Felladrin/MiniSearch/resolve/main/server/utils/onnxModelLoader.ts
5.76 kB
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| import { Tokenizer } from "@huggingface/tokenizers"; | |
| import debug from "debug"; | |
| import { InferenceSession } from "onnxruntime-node"; | |
| import { downloadFileFromHuggingFaceRepository } from "../downloadFileFromHuggingFaceRepository.ts"; | |
| const SERVER_DIR = path.resolve( | |
| path.dirname(fileURLToPath(import.meta.url)), | |
| "..", | |
| ); | |
| const printMessage = createModelLogger(path.basename(import.meta.url)); | |
| /** | |
| * Creates a debug logger with the same enabled-always behavior used by the | |
| * model services. | |
| */ | |
| export function createModelLogger(moduleName: string) { | |
| const printMessage = debug(moduleName); | |
| printMessage.enabled = true; | |
| return printMessage; | |
| } | |
| /** | |
| * Session knobs a caller may override. Left unset, ONNX Runtime picks its own | |
| * defaults, which is what every caller but the bi-encoder worker wants. | |
| */ | |
| interface OnnxSessionOptions { | |
| intraOpNumThreads?: number; | |
| } | |
| export interface LoadOnnxModelOptions { | |
| /** Tokenizer file names, for a repo that does not use the standard ones. */ | |
| tokenizerFile?: string; | |
| tokenizerConfigFile?: string; | |
| sessionOptions?: OnnxSessionOptions; | |
| } | |
| function resolveModelPath(modelRepo: string, hfRepoFile: string) { | |
| return path.resolve(SERVER_DIR, "models", modelRepo, hfRepoFile); | |
| } | |
| // Replaces a cached file when its size differs from the Hub, not just when | |
| // missing. | |
| async function ensureModelFileExists(modelRepo: string, hfRepoFile: string) { | |
| const localPath = resolveModelPath(modelRepo, hfRepoFile); | |
| await downloadFileFromHuggingFaceRepository(modelRepo, hfRepoFile, localPath); | |
| return localPath; | |
| } | |
| // CPU-only, errors-only logging. The reranker is a dynamically quantized | |
| // graph, the wrong shape for WebGPU (no kernels for the integer matmuls, | |
| // shuttles them to the CPU: 812ms against 172ms for the same work, with scores | |
| // drifting by up to 1.15 and reordering results). `coreml` is slower than CPU | |
| // on dynamic shapes. The bi-encoder is fp32 and was not benchmarked on | |
| // accelerators, but inherits this choice. logSeverityLevel 3 keeps startup | |
| // quiet: ONNX Runtime otherwise warns that it assigned shape operators to CPU, | |
| // which is expected and not actionable. The architecture is logged because it | |
| // selects the quantized kernel, which is the part that varies between hosts. | |
| function createOnnxSession( | |
| modelRepo: string, | |
| modelPath: string, | |
| sessionOptions: OnnxSessionOptions = {}, | |
| ) { | |
| printMessage( | |
| `Creating CPU session for ${modelRepo} (arch: ${process.arch}, platform: ${process.platform}${ | |
| sessionOptions.intraOpNumThreads === undefined | |
| ? "" | |
| : `, intra-op threads: ${sessionOptions.intraOpNumThreads}` | |
| })...`, | |
| ); | |
| return InferenceSession.create(modelPath, { | |
| executionProviders: ["cpu"], | |
| logSeverityLevel: 3, | |
| ...sessionOptions, | |
| }); | |
| } | |
| /** | |
| * Resolves the tokenizer's real pad token id from its config. Returns the | |
| * resolved id, or `null` for anything it cannot cleanly resolve: no | |
| * `pad_token` key, an empty value, a shape it does not understand, or a | |
| * `token_to_id` miss. It never throws β this loader is shared, and a pad | |
| * id it cannot read must not take a service down at boot. The caller that | |
| * actually needs a pad id (the bi-encoder) decides what to do about null. | |
| * | |
| * Both serializations transformers emit are accepted: a plain string, and | |
| * the AddedToken dict form (e.g. `{"content": "<pad>", "lstrip": false, | |
| * "rstrip": false, "normalized": true}`), which the cached configs here | |
| * already use for `mask_token`. Padding with an assumed id is never on | |
| * the table: in the XLM-RoBERTa exports id 0 is `<s>`, not `<pad>`. | |
| */ | |
| function resolvePadTokenId( | |
| tokenizer: Tokenizer, | |
| tokenizerConfig: { pad_token?: unknown }, | |
| ): number | null { | |
| const padToken = tokenizerConfig?.pad_token; | |
| let padTokenString: string | null = null; | |
| if (typeof padToken === "string" && padToken.length > 0) { | |
| padTokenString = padToken; | |
| } else if ( | |
| typeof padToken === "object" && | |
| padToken !== null && | |
| typeof (padToken as { content?: unknown }).content === "string" && | |
| (padToken as { content: string }).content.length > 0 | |
| ) { | |
| padTokenString = (padToken as { content: string }).content; | |
| } | |
| if (padTokenString === null) { | |
| return null; | |
| } | |
| if (typeof tokenizer.token_to_id !== "function") { | |
| return null; | |
| } | |
| const padTokenId = tokenizer.token_to_id(padTokenString); | |
| return padTokenId === undefined ? null : padTokenId; | |
| } | |
| /** | |
| * Downloads model files from a Hugging Face repo and returns a ready | |
| * inference session, tokenizer, and the tokenizer's real pad token id. The | |
| * tokenizer files use the standard Hugging Face names when not overridden. | |
| */ | |
| export async function loadOnnxModel( | |
| modelRepo: string, | |
| modelFile: string, | |
| { | |
| tokenizerFile = "tokenizer.json", | |
| tokenizerConfigFile = "tokenizer_config.json", | |
| sessionOptions = {}, | |
| }: LoadOnnxModelOptions = {}, | |
| ): Promise<{ | |
| session: InferenceSession; | |
| tokenizer: Tokenizer; | |
| padTokenId: number | null; | |
| }> { | |
| const [modelPath, tokenizerPath, tokenizerConfigPath] = await Promise.all([ | |
| ensureModelFileExists(modelRepo, modelFile), | |
| ensureModelFileExists(modelRepo, tokenizerFile), | |
| ensureModelFileExists(modelRepo, tokenizerConfigFile), | |
| ]); | |
| const tokenizerConfig = JSON.parse( | |
| fs.readFileSync(tokenizerConfigPath, "utf8"), | |
| ) as { pad_token?: unknown }; | |
| const tokenizer = new Tokenizer( | |
| JSON.parse(fs.readFileSync(tokenizerPath, "utf8")), | |
| tokenizerConfig, | |
| ); | |
| const padTokenId = resolvePadTokenId(tokenizer, tokenizerConfig); | |
| const session = await createOnnxSession(modelRepo, modelPath, sessionOptions); | |
| return { session, tokenizer, padTokenId }; | |
| } | |