The rampart-langtools modules

Preface

Acknowledgment

The rampart-langtools package provides five modules built on best-in-class machine-learning libraries:

  • The rampart-llamacpp module is built on llama.cpp, the C/C++ LLM inference engine created by Georgi Gerganov and contributors.
  • The rampart-clip module is built on clip.cpp, a CLIP inference implementation by Yusuf Sarıgöz, ported onto the same ggml tensor library that rampart-llamacpp uses.
  • The rampart-onnx module is built on ONNX Runtime, the cross-platform inference engine for ONNX models from Microsoft (with tokenizers from onnxruntime-extensions).
  • The rampart-faiss module is built on FAISS, the library for efficient similarity search and clustering of dense vectors from Meta AI Research.
  • The rampart-sentencepiece module is built on SentencePiece, the unsupervised text tokenizer from Google.

The authors of Rampart extend their thanks to the authors and contributors of each of these libraries.

License

The llama.cpp library is licensed under the MIT License. The clip.cpp implementation (and the ggml library it uses) is licensed under the MIT License. The ONNX Runtime library is licensed under the MIT License. The FAISS library is licensed under the MIT License. The SentencePiece library is licensed under the Apache 2.0 License.

The rampart-langtools modules are released under the MIT license.

What do they do?

Together the modules provide the building blocks for semantic search and local LLM inference inside Rampart:

  • rampart-llamacpp runs GGUF models directly inside the rampart process: text embedding (initEmbed), reranking (initRerank) and text generation (initGen).
  • rampart-onnx runs ONNX models: text embedding (onnx.initEmbed) and reranking (onnx.initRerank) with the same handle API as rampart-llamacpp, plus a general-purpose session API (onnx.initSession) for running any ONNX model.
  • rampart-clip runs CLIP models: it embeds images and text into one shared vector space (clip.initEmbed), so a text query can search a collection of images, or an image can find similar images — cross-modal semantic search.
  • rampart-faiss builds, trains, saves and searches vector indexes, from small exact-search indexes to compressed indexes holding hundreds of millions of vectors.
  • rampart-sentencepiece tokenizes text into subword pieces (and back) using a SentencePiece model.

The package also ships rampart-models, a pure-JavaScript helper that downloads and locates the models the engines above consume — models.get('bge-m3') returns a ready-to-use local path, fetching from HuggingFace on first use. See The rampart-models module.

A typical pipeline embeds documents with rampart-llamacpp or rampart-onnx, stores the vectors in a rampart-faiss index (and/or a rampart-sql table), searches that index with an embedded query vector, and optionally reranks the results with a reranking model.

Note that the rampart-sql module has this pipeline built in: its embed() SQL function generates vectors in the SQL engine using this package’s rampart-llamacpp module, and its CREATE VECTOR INDEX / LIKEV similarity search uses a FAISS-backed (IVFPQ) index internally. When vectors live in a SQL table, that integrated path is usually the simplest choice — see Vector Search in the rampart-sql documentation. The modules documented here are for using the same engines directly: custom or standalone faiss indexes, embedding outside the SQL engine, reranking and text generation.

Platform Availability

The rampart-langtools modules are not available on the following platforms:

  • macOS x86_64 (Intel Macs).
  • 32-bit ARM Linux (the raspberry_pi_os-buster-armv7l build).

On macOS (Apple Silicon):

  • initEmbed requires macOS 12 (Monterey) or later.
  • initGen requires macOS 15 (Sequoia) or later, due to a Metal regression in older versions of macOS. The requirement is checked at runtime and initGen will throw a descriptive error on macOS 14 and below.

On Linux, the modules are available in CPU and CUDA (GPU) builds. GPU-only features (such as the faiss idx.enableGpu() function) are noted where applicable.

The rampart-onnx module additionally requires glibc 2.28 or later on Linux (it is not included in the packages built for older distributions), and its CUDA support requires an NVIDIA driver supporting CUDA 12 or later — see CPU and GPU (runtime selection).

Errors, Warnings and Logs

The modules never write to stdout or stderr. Instead:

  • A failure throws a JavaScript Error (a model that cannot be loaded, a malformed tensor, a session used after destroy()). Catch it as usual.
  • A warning — a non-fatal problem that did not stop the call — is placed in an errMsg property, exactly as rampart-sql does. The property is set on the object the call was made on: the module object for onnx.initEmbed(), a handle for a handle method. It is cleared at the start of every call, so it always describes the most recent one, and it is undefined when the call had nothing to report. Typical warnings are a GPU that could not be used and silently fell back to the CPU, or an unusable RAMPART_ONNX_RUNTIME override.
var emb = onnx.initEmbed(model);
if (onnx.errMsg)                       // e.g. "no usable GPU; using CPU"
    console.log("warning: " + onnx.errMsg);

errMsg is deliberately kept separate from getLog / onnx.getLog / onnx.clearLog, which capture the informational output of the underlying libraries (ggml/llama.cpp and ONNX Runtime). That log is verbose — a single embedding can produce thousands of lines — and a warning placed there would be lost in it. Use errMsg to find out whether something went wrong, and getLog() when you want the engine’s own diagnostics.

The rampart-llamacpp module

Loading the module is a simple matter of using the require() function:

var llamacpp = require("rampart-llamacpp");

Models are standard GGUF files, as downloaded from, e.g., Hugging Face.

modelInfo

The modelInfo function returns a model’s key parameters by reading only its GGUF metadata and vocabulary. It does not load the weight tensors, so there is no GPU upload and the call is fast even for multi-gigabyte models.

Usage:

var llamacpp = require("rampart-llamacpp");

var info = llamacpp.modelInfo(path);

Where path is a String, the path to a .gguf model file.

Return Value:

An Object with the following properties:

  • embedDim - A Number, the size of the vector that initEmbed’s embedding functions produce. It prefers the GGUF embedding_length_out of a projection head, falling back to embedding_length.
  • hiddenDim - A Number, the model hidden size.
  • nCtxTrain - A Number, the trained context length.
  • nLayer - A Number, the number of layers.
  • arch - A String, the GGUF general.architecture (e.g. "bert", "llama").
  • pooling - A String, the model’s declared pooling type: "none", "mean", "cls", "last", "rank" or "unspecified".
  • nParams - A Number, the parameter count.

Example:

var llamacpp = require("rampart-llamacpp");

var info = llamacpp.modelInfo("bge-m3-FP16.gguf");
/* {
      embedDim:   1024,
      hiddenDim:  1024,
      nCtxTrain:  8192,
      nLayer:     24,
      arch:       "bert",
      pooling:    "cls",
      nParams:    566703104
   } */

/* size vector storage from the model itself: */
var vecDim = info.embedDim;

initEmbed

The initEmbed function loads an embedding model and returns a handle used to convert text into semantic vectors.

Usage:

var llamacpp = require("rampart-llamacpp");

var emb = llamacpp.initEmbed(path[, options]);

Where:

  • path is a String, the path to a .gguf embedding model.

  • options is an optional Object accepting all of the settings in Common Model and Context Options below, plus the following embedding-specific options:

    • pooling - A String, one of "none", "mean", "cls", "last" or "rank" (--pooling in llama-server). Default: the model’s own declared pooling, read from its GGUF metadata, falling back to "mean" only if the model declares none.

      Most embedding models do declare one, and they disagree: the bge-* family uses "cls", nomic-embed-text and all-MiniLM use "mean", Qwen3-Embedding uses "last". Setting this by hand is therefore usually a mistake — it overrides what the model was trained with and produces vectors unlike the model’s intended output. modelInfo reports what a given file declares, without loading its weights.

    • attention - A String, "causal" or "non-causal" (--attention).

    • split - A String or Function. "auto" (the default) chunks long text on its structure — paragraphs, merged or packed as configured below; "window" uses plain token windows. A Function replaces the built-in chunker entirely: it is called with the document text and must return an Array of Strings — N strings always produce N vectors. A string that fits the token window gets its exact vector; an oversized string gets its combined (average) vector over its sub-chunks, and its chunks entry is marked oversized. The strings may freely transform the input (inject a title, drop boilerplate, …), so the returned chunks carry {text, tokens} without byte spans. Two notes: a splitter shared with other rampart threads (via a copied handle) must be self-contained — it cannot reference variables outside itself; and the SQL chunkembed() / 5-argument abstract() machinery always uses the built-in chunker, so custom splitters are for JS-side pipelines. (The built-in chunker is the same as onnx.initEmbed’s — the two modules share it, so identical options produce identical chunk boundaries for the same tokenizer.)

    • minTokens - A Number, the paragraph-fragment floor: shorter paragraphs are merged with a neighbor. -1 disables merging.

    • packParagraphs - A Boolean. If true, consecutive paragraphs are packed together up to the token window (fewer, fuller chunks) instead of one vector per paragraph.

    • sentenceSplit - A Boolean. If true, an oversized paragraph (or structureless text) is split at sentence boundaries and the sentences greedily packed to the token window, instead of being cut at raw token windows mid-sentence. Boundaries come from a multi-script terminator table (ASCII .!? with a whitespace guard; self-delimiting CJK 。!?, Arabic, Devanagari and others with none; fullwidth digit-guarded so 3.14 never splits), and a chunk never ends on a tiny trailing fragment (so a false boundary after “Mr.” can’t take a cut). Languages without sentence punctuation (e.g. Thai) fall back to token windows. Default false: enabling it changes chunk boundaries, which tables built WITHOUT value headers depend on for snippet spans.

    • batchChunks / batchTokens - Per-handle overrides of the process-wide chunk-batching defaults. See embedDefaults for what they control, what they are worth on each backend, and why batchTokens should not simply be raised. Given here they apply to this handle only, and take precedence over the embedDefaults values.

    The legacy option names nctx, ubatch, nthreads and nthreads_batch are accepted as aliases for nCtx, nUBatch, threads and threadsBatch.

If nCtx is not given, the context size defaults to the model’s trained maximum, capped at 8192 tokens.

Model weights are shared: loading the same model file from several handles or rampart threads keeps a single copy of the weights in memory. A handle that is copied to another rampart thread transparently builds its own per-thread context on first use.

Note:
The rampart-sql embed() SQL function runs this same engine inside the SQL module: sql.set({llamaEmbed: '/path/to/model.gguf'}) loads the model through rampart-llamacpp and embed(?) then produces the same vectors as emb.embedTextToFp16Buf()’s avgVec. When embedding rows of a SQL table, that path avoids round-trips through JavaScript — see Generating embeddings and the llamaEmbed property.
Return Value:
An Object (the embedding handle) with the functions emb.embedTextToFp16Buf(), emb.embedTextToFp32Buf(), emb.embedTextToNumbers() and emb.destroy() documented below.

Example:

var llamacpp = require("rampart-llamacpp");

var emb = llamacpp.initEmbed("all-minilm-l6-v2_f16.gguf");

var v = emb.embedTextToFp16Buf("about a paragraph of text ...");

/* v = { vecs: [vec1, ...], avgVec: avg, coherence: n,
         chunks: [ {start,end,tokens,text}, ... ] }
   If the text fits the model's context window,
   v.vecs.length == 1 and v.vecs[0] == v.avgVec.       */

/* store the vector, e.g., in a sql table */
sql.exec("insert into vecs values(?,?,?)",
         [v.avgVec, docId, text]);

/* unload when no longer needed */
emb.destroy();

emb.embedTextToFp16Buf()

Embed text and return the vector(s) packed as 16-bit floats (little-endian) in a Buffer. This is the most compact format and is directly usable by rampart.vector functions, the rampart-sql vecdist() function and the faiss idx.addFp16() / idx.searchFp16() functions.

Usage:

var ret = emb.embedTextToFp16Buf(text);

Where text is a String (or a Buffer containing text) to be embedded.

If the tokenized text does not fit the model’s context window, it is chunked; with the default split: "auto" the chunking is structure-aware: text is split on paragraph boundaries (falling back to token windows for oversized paragraphs), so each vector corresponds to a semantically meaningful span of the input, and the spans are reported in chunks. Each vector is L2-normalized.

Return Value:

An Object with the following properties:

  • vecs - An Array of Buffers, one vector per chunk (2 * embedDim bytes each).
  • avgVec - A Buffer. If only one chunk was produced, the same vector as vecs[0]. Otherwise the re-normalized average of all the chunk vectors, suitable as a single whole-document vector.
  • coherence - A Number in [0, 1]: the average pairwise cosine similarity of the chunk vectors (1.0 for a single-chunk document). A low value means the document spans several topics and avgVec is a blurrier summary of it.
  • chunks - An Array of Objects, one per vector, with start / end (the chunk’s byte span in the input), tokens (its token count), text (the chunk text itself) and oversized (Boolean true when this vector is one of several token windows over a single span that exceeded the model window; such sub-chunks share their span).

For empty or whitespace-only input, vecs is an empty Array and avgVec is not set.

emb.embedTextToFp32Buf()

The same as emb.embedTextToFp16Buf() except that vectors are packed as 32-bit floats (4 * embedDim bytes per vector).

Usage:

var ret = emb.embedTextToFp32Buf(text);

emb.embedTextToNumbers()

The same as emb.embedTextToFp16Buf() except that each vector is returned as an Array of Numbers rather than a packed Buffer.

Usage:

var ret = emb.embedTextToNumbers(text);

emb.destroy()

Free the model context (and release the model weights if this was the last handle using them). Using the handle after calling destroy() throws an error. Handles are also freed automatically when garbage collected, but for large models it is good practice to free them deterministically.

Usage:

emb.destroy();

initRerank

The initRerank function loads a reranking model (such as bge-reranker-v2-m3) and returns a handle used to score how well documents answer a query. Reranking is commonly applied to the top results of a vector search to improve final ordering.

Usage:

var llamacpp = require("rampart-llamacpp");

var rr = llamacpp.initRerank(path[, options]);

Where:

  • path is a String, the path to a .gguf reranking model.
  • options is an optional Object accepting the same settings as initEmbed above. For reranking, pooling defaults to "rank", the context size defaults to the model’s trained maximum capped at 1024 tokens, and nUBatch defaults to 512. Input longer than nUBatch tokens is truncated.
Return Value:
An Object (the reranker handle) with the functions rr.rerank() and destroy() (as in emb.destroy()).
Note:
Instruct-style rerankers — the Qwen3-Reranker family — judge relevance by answering a yes/no question inside a chat prompt rather than through a bert-style classifier head. initRerank detects them by model architecture and wraps each (query, document) pair in the required prompt automatically. Their scores are the probability of “yes” (roughly 0.5 – 1.0 for plausible documents) rather than the wide-range scores of bert-style rerankers; orderings are comparable, magnitudes are not. Beware that many community GGUF conversions of these models were made as plain language models and lack the ranking head — such a file loads but scores every document identically. The catalog’s models.ggufGet("qwen3-reranker-0.6b") is pinned to a verified conversion.

Example:

var llamacpp = require("rampart-llamacpp");

var rr = llamacpp.initRerank("bge-reranker-v2-m3-Q8_0.gguf");

var question = "How tall is the Eiffel Tower?";

/* score a single document */
var score = rr.rerank(question,
    "The Eiffel Tower is 330 metres tall.");

/* score several documents at once */
var scored = rr.rerank(question, [
    "The Eiffel Tower is 330 metres tall.",
    "Gustave Eiffel also designed bridges.",
    "Paris is the capital of France."
]);
/* [ { document: "The Eiffel Tower is ...", score: 4.21 },
     { document: "Gustave Eiffel also ...", score: -1.7 },
     ...                                                 ] */

rr.destroy();

rr.rerank()

Score one or more documents against a query.

Usage:

var score  = rr.rerank(query, document);
var scored = rr.rerank(query, documents[, scoresOnly]);

Where:

  • query is a String, the question or search text.
  • document/documents is a String (a single document) or an Array of Strings.
  • scoresOnly is an optional Boolean (only meaningful with an Array). Default false.
Return Value:
  • Given a single String document: a Number, the relevance score. Higher is more relevant; the range depends on the model (scores are typically logits, not probabilities).
  • Given an Array of documents: an Array of Objects, each {document: String, score: Number}, in the same order as the input.
  • Given an Array and scoresOnly = true: an Array of Numbers.

initGen

The initGen function loads a text-generation model and returns a handle for synchronous and streaming generation.

Experimental. initGen, predict and predictAsync are under active development and the API may change. Vision / multimodal input (mmproj) is not supported.

initGen runs a single shared, continuously-batched engine on a dedicated rampart thread. When the returned handle is shared across rampart threads (e.g. server threads), their requests are transparently pooled into that one engine: one copy of the model in memory, batched decoding. nSeqMax sets how many requests may decode together.

On macOS, initGen requires macOS 15 (Sequoia) or later — see Platform Availability.

Usage:

var llamacpp = require("rampart-llamacpp");

var gen = llamacpp.initGen(path[, options]);

Where:

  • path is a String, the path to a .gguf text-generation model.
  • options is an optional Object accepting all of the settings in Common Model and Context Options below, plus the following generation-specific options:
    • jinja - A Boolean, whether to apply the model’s chat template via Jinja when messages are given to gen.predict(). Default: true.
    • chatTemplate - A String, a custom Jinja chat template overriding the model’s built-in template.
    • chatTemplateFile - A String, a file from which to read the custom chat template.
Return Value:
An Object (the gen handle) with the properties nCtx and nVocab (Numbers, the resolved context size and vocabulary size) and the functions gen.predict(), gen.predictAsync(), gen.getLast() and gen.destroy().

Example:

var llamacpp = require("rampart-llamacpp");

var gen = llamacpp.initGen("gemma-3-4b-it-Q4_K_M.gguf", {
    nCtx:    4096,
    nSeqMax: 4      /* up to 4 requests batched together */
});

/* synchronous: blocks and returns the full text */
var text = gen.predict({
    messages: [
        { role: "system", content: "You are a helpful assistant." },
        { role: "user",   content: "What is the capital of France?" }
    ],
    maxTokens: 128
});
rampart.utils.printf("%s\n", text);

/* streaming: tokens are delivered as they are produced */
var h = gen.predictAsync(
    { prompt: "Explain how a combustion engine works.",
      maxTokens: 256, temp: 0.7 },
    function(res) {   /* per token */
        if (!res.done && !res.error)
            rampart.utils.printf("%s", res.token);
    },
    function(res) {   /* done: res.fullText, res.error */
        rampart.utils.printf("\n[done]\n");
    }
);
/* h.cancel();  -- stop this generation early */

gen.destroy();

gen.predict()

Generate text synchronously. The call blocks the current thread until generation completes and returns the full generated text. On a server, this blocks the worker thread’s event loop — use gen.predictAsync() in hot handlers.

Usage:

var text = gen.predict(options);

Where options is an Object with the following properties (one of prompt or messages is required):

  • prompt - A String, a plain text prompt.
  • messages - An Array of {role: String, content: String} Objects, chat-style messages. The model’s chat template is applied (see the jinja option of initGen).
  • maxTokens - A Number, the maximum number of tokens to generate. Default: 512.
  • temp - A Number, the sampling temperature.
  • topP - A Number, top-p (nucleus) sampling.
  • topK - A Number, top-k sampling.
  • minP - A Number, min-p sampling.
  • repeatPenalty - A Number, the repetition penalty.
  • repeatLastN - A Number, how many recent tokens the repetition penalty considers.
  • seed - A Number, the RNG seed. If not given, a random seed is used per request.
  • stop - An Array of Strings; generation stops when any of them is produced.
  • addAssistant - A Boolean, whether to append the assistant generation prompt when applying a chat template. Default: true.

Unset sampling options use the model/engine defaults.

Return Value:
A String, the full generated text. If the engine reports an error, the returned string is "[gen err:<message>]".

gen.predictAsync()

Generate text asynchronously, streaming tokens as they are produced. The call returns immediately; callbacks fire from the event loop. Multiple in-flight calls (across threads, or from one event loop) batch together through the shared engine.

Usage:

var h = gen.predictAsync(options, perToken[, final]);

Where:

  • options is the same Object accepted by gen.predict().
  • perToken is a Function, called once per generated token with an Object:
    • token - A String, the token text.
    • done - A Boolean, false for token callbacks.
  • final is an optional Function, called once when the generation ends, with an Object:
    • fullText - A String, the complete generated text.
    • error - A String, set if the generation failed.
Return Value:
An Object with a single function cancel(), which stops this generation early and frees its slot in the engine.

gen.getLast()

Return the full text of the last completed gen.predict() call made through this handle on the current thread.

Usage:

var text = gen.getLast();

gen.destroy()

Shut down the shared generation engine and free the model context. The handle (and any copies of it on other threads) must not be used afterwards.

Usage:

gen.destroy();

Common Model and Context Options

initEmbed, initRerank and initGen accept a common set of model-loading and context options. Most map 1:1 onto the matching llama-server command-line flag (the camelCase of the flag); see the llama.cpp server documentation for full descriptions of each.

Option llama-server flag Value
gpuLayers --gpu-layers Number. Layers to offload to the GPU (-1 = all).
mainGpu --main-gpu Number. GPU device to use.
splitMode --split-mode String: "none", "layer" or "row".
useMmap --no-mmap Boolean. Memory-map the model file.
useMlock --mlock Boolean. Lock the model in RAM.
checkTensors --check-tensors Boolean. Validate tensor data while loading.
nCtx --ctx-size Number. Context size in tokens (0 or -1 = the model’s trained maximum).
nBatch --batch-size Number. Logical batch size.
nUBatch --ubatch-size Number. Physical (micro-)batch size.
nSeqMax --parallel Number. Maximum parallel sequences (initGen: how many requests decode together).
threads --threads Number. Threads for generation.
threadsBatch --threads-batch Number. Threads for batch/prompt processing.
flashAttn --flash-attn Boolean or String: "on", "off" or "auto".
cacheTypeK --cache-type-k String. KV cache type for K: "f32", "f16", "bf16", "q8_0", "q4_0", "q4_1", "q5_0", "q5_1" or "iq4_nl".
cacheTypeV --cache-type-v String. KV cache type for V (same values).
offloadKqv --no-kv-offload Boolean. Offload the KV cache to the GPU. offloadKQV is accepted as an alias.
opOffload --op-offload Boolean. Offload host-tensor operations.
kvUnified --kv-unified Boolean. Use a unified KV cache.
ropeScaling --rope-scaling String: "none", "linear", "yarn" or "longrope".
ropeFreqBase --rope-freq-base Number.
ropeFreqScale --rope-freq-scale Number.
yarnExtFactor --yarn-ext-factor Number.
yarnAttnFactor --yarn-attn-factor Number.
yarnBetaFast --yarn-beta-fast Number.
yarnBetaSlow --yarn-beta-slow Number.
yarnOrigCtx --yarn-orig-ctx Number.

embedDefaults

The embedDefaults function gets and sets process-global defaults for embedding. They seed initEmbed’s options — an option given on the initEmbed call itself still wins — and they are the only way to configure embedding done inside rampart-sql (via sql.set({llamaEmbed:…})), which loads the model through this module but takes no options object of its own.

Usage:

var llamacpp = require("rampart-llamacpp");

var settings = llamacpp.embedDefaults([options]);

Where options is an optional Object with any of the following properties. Omitting the argument entirely returns the current settings without changing them.

  • batchChunks - A Boolean, Number or null. Controls how many of a document’s chunks are packed into a single decode. null (the default) means auto: batch on a GPU backend, one chunk per decode on CPU. true packs as many as the context allows, false packs one at a time, and a Number caps the sequences per decode.

    Experimental. Chunk batching is under active development. It has been verified for correctness on CUDA and Metal, but its performance is hardware-dependent and it is untested on some backends, so the defaults may change. false always gives the unbatched behavior exactly.

    Auto is the recommended setting. Batching is measurably faster on a GPU backend and no faster at all on CPU, and it perturbs the resulting vectors slightly — a larger batch selects different matmul kernels, which moves each element on the order of 1e-3 (cosine similarity ~0.9999 against the unbatched vector). It never changes how text is chunked: the chunk count and byte offsets are identical either way.

  • batchTokens - A Number, the soft cap on total tokens in one packed decode. Default 512.

    Raising it is not a way to go faster, and usually does the opposite: a packed batch’s attention cost grows quadratically with its token count, while the per-decode overhead batching saves grows only linearly. Past a few hundred tokens the quadratic wins, and batching can end up slower than not batching at all. 512 is a tuned value — re-measure before changing it.

    A chunk longer than the cap is never split; it goes through on its own.

  • threads - A Number, the per-token decode thread count (n_threads). Default 1.

  • threadsBatch - A Number, the multi-token decode thread count (n_threads_batch). This is the one that matters for embedding, where every decode is multi-token.

    Default -1, which hands the choice to ggml — and ggml chooses 4 regardless of the machine’s core count. On anything larger than a 4-core box, set this explicitly.

Return Value:
An Object with the settings in effect after applying any changes: batchChunks, batchTokens, threads, threadsBatch, plus gpuInUse, a Boolean reporting whether a GPU backend is registered in this process — i.e. which way batchChunks: null will resolve. ggml registers its GPU backend when the first model loads, so gpuInUse reads false on a GPU machine until then; the auto decision itself is always made after a load and is unaffected.
Note:
Call this before the model is loaded. Models are cached by path: the second and later initEmbed or sql.set({llamaEmbed: …}) for the same file hand back the already-loaded model unchanged, and an existing context keeps the thread counts it was built with. An embedDefaults call made afterwards will silently not apply to that model.

Example — overriding the batching default on the SQL path:

var Sql = require("rampart-sql");

/* BEFORE sql.set({llamaEmbed: ...}), and before any other
   code loads the same model.                              */
require("rampart-llamacpp").embedDefaults({
    batchChunks:  false,   /* one chunk per decode        */
    threadsBatch: 8        /* else ggml uses 4, always    */
});

var sql = new Sql("/path/to/db");
sql.set({llamaEmbed: "/models/bge-m3-FP16.gguf"});

sql.exec("insert into docs values(?, chunkembed(?))", [id, text]);

Reasons to turn batching off include reproducing vectors generated by a build that predates it, and A/B measuring the difference on a particular machine. For ordinary use, leave it at auto.

getLog

llama.cpp produces log output during model loading and initialization. This output is captured in an internal buffer rather than printed to stdout/stderr. getLog retrieves the captured log.

Usage:

var llamacpp = require("rampart-llamacpp");

var emb = llamacpp.initEmbed("all-minilm-l6-v2_f16.gguf");

var log = llamacpp.getLog();
console.log(log);
Return Value:
A String, the captured log output.
Note:
The log buffer has a maximum size of 40KB. If it overflows, the oldest half of the log is discarded and the first line will read WARN: log overflow. The log buffer is process-global (all threads write to the same, mutex-protected buffer), but getLog/resetLog are only serviceable from the module object of the thread that first loaded the module; on other threads they throw.

resetLog

Clear the captured log buffer. See getLog.

Usage:

llamacpp.resetLog();

Environment Variables

  • RAMPART_LLAMA_CUDA_GRAPHS - On CUDA builds, ggml caches a captured CUDA graph per compute-graph shape and only evicts entries after they have been idle for 10 seconds. Batched embedding and reranking decode a stream of varying shapes, which fills that cache faster than it drains and makes GPU memory climb until it runs out. CUDA graphs only speed up single-stream text generation, so initEmbed, initRerank and the rampart-sql embedding path disable them automatically. A process that only calls initGen never disables them, so generation performance is unaffected. Set RAMPART_LLAMA_CUDA_GRAPHS (to any value) to opt out and keep CUDA graphs on even for embedding/reranking, accepting the memory growth above. The setting is process-global and read once at startup. It has no effect on CPU or Metal (Apple) builds.
  • RAMPART_METAL_RESIDENCY - On macOS, Metal “residency sets” are disabled by default to avoid an assertion at process exit. Set RAMPART_METAL_RESIDENCY=1 to keep the feature (a marginal performance optimization) enabled.

The rampart-onnx module

Experimental. rampart-onnx is new in this release. It is under active development and its API may change.

Loading the module is a simple matter of using the require() function:

var onnx = require("rampart-onnx");

The module runs models in the ONNX format via ONNX Runtime. It provides two layers:

For embedding and reranking, the simplest input is a HuggingFace model directory (e.g. a git clone of all-MiniLM-L6-v2): the .onnx file, the tokenizer, the pooling mode and the token window are all discovered from the directory contents. A bare .onnx file path also works, but then a tokenizer must be supplied.

CPU and GPU (runtime selection)

A single rampart-onnx.so serves both CPU and GPU: the module contains a complete CPU-only ONNX Runtime, and GPU installs add an optional CUDA runtime directory (onnx-cu12/ or onnx-cu13/) next to the module. At first use the module picks a runtime:

  1. If the environment variable RAMPART_ONNX_RUNTIME is set (cpu, cu12, cu13 or an absolute directory path), it wins.
  2. Otherwise, if an NVIDIA driver supporting CUDA 12 or later is present and reports at least one GPU, the newest CUDA runtime directory the driver supports is used, preferring one built for the GPU’s exact compute capability.
  3. Otherwise (or if the chosen runtime fails to load), the built-in CPU runtime is used.

onnx.runtimeInfo reports which runtime was picked. Selecting a GPU runtime makes GPU execution available, and sessions then use it automatically: on a build where a GPU runtime was selected, onnx.initSession, onnx.initEmbed and onnx.initRerank run on the GPU by default (the same auto-GPU behavior as the rampart-sql embedding path). Pass gpu: false (or provider: "cpu") to force CPU. A session that requests the GPU but cannot create one (no device, or a driver problem) falls back to CPU with a one-line notice rather than failing.

onnx.initEmbed

The initEmbed function loads an ONNX embedding model and returns a handle used to convert text into semantic vectors.

Usage:

var onnx = require("rampart-onnx");

var oemb = onnx.initEmbed(modelPath[, options]);

Where:

  • modelPath is a String: a HuggingFace-layout model directory (recommended), or the path of a .onnx file. Given a directory, the module discovers the .onnx model file, the tokenizer (a *vocab.txt selects WordPiece, otherwise tokenizer.json selects a SentencePiece/BPE tokenizer), the pooling mode (from 1_Pooling/config.json) and the model’s token window. Given a bare .onnx file, the module still tries to discover the tokenizer beside the model — in the file’s own directory and, when the file sits in an onnx/ subdirectory (the common HuggingFace layout), in its parent — so pointing at a specific .onnx (e.g. an fp16 variant) usually works without options.tokenizer; supply it explicitly only when discovery cannot find one.
  • options is an optional Object accepting all of the session options of onnx.initSession (notably gpu), plus the following. Every discovered setting can be overridden here.
    • tokenizer - A String (the path of a SentencePiece model, loaded via rampart-sentencepiece) or an Object with an encodeIds(text) function (e.g. from onnx.wordPieceTokenizer or onnx.spTokenizer, or custom JavaScript).
    • pooling - A String, "mean" or "cls". Default: the directory’s declared pooling, else "mean".
    • normalize - A Boolean, L2-normalize each vector. Default: true.
    • maxTokens - A Number, the per-chunk token window. Default: the model’s discovered positional capacity (capped at 8192), else 512.
    • queryPrefix / passagePrefix - Strings prepended to query / passage text before embedding, for models trained with instruction prefixes (e.g. e5’s "query: " / "passage: "). See the isQuery argument of oemb.embedTextToFp16Buf().
    • split - A String or Function. "auto" (the default) chunks long text on its structure — paragraphs, merged or packed as configured below; "window" uses plain token windows. A Function replaces the built-in chunker entirely: it is called with the document text and must return an Array of Strings — N strings always produce N vectors. A string that fits the token window gets its exact vector; an oversized string gets its combined (average) vector over its sub-chunks, and its chunks entry is marked oversized. The strings may freely transform the input (inject a title, drop boilerplate, …), so the returned chunks carry {text, tokens} without byte spans. Two notes: a splitter shared with other rampart threads (via a copied handle) must be self-contained — it cannot reference variables outside itself; and the SQL chunkembed() / 5-argument abstract() machinery always uses the built-in chunker, so custom splitters are for JS-side pipelines.
    • minTokens - A Number, the paragraph-fragment floor: shorter paragraphs are merged with a neighbor. -1 disables merging.
    • packParagraphs - A Boolean. If true, consecutive paragraphs are packed together up to the token window (fewer, fuller chunks) instead of one vector per paragraph.
    • sentenceSplit - A Boolean. If true, an oversized paragraph (or structureless text) is split at sentence boundaries and the sentences greedily packed to the token window, instead of being cut at raw token windows mid-sentence. Boundaries come from a multi-script terminator table (ASCII .!? with a whitespace guard; self-delimiting CJK 。!?, Arabic, Devanagari and others with none; fullwidth digit-guarded so 3.14 never splits), and a chunk never ends on a tiny trailing fragment (so a false boundary after “Mr.” can’t take a cut). Languages without sentence punctuation (e.g. Thai) fall back to token windows. Default false: enabling it changes chunk boundaries, which tables built WITHOUT value headers depend on for snippet spans.
    • maxChunkBatch - A Number, the maximum chunks per batched model run (bounds memory for many-chunk documents). Default: 64 (CPU) or 32 (GPU).
    • bosId, eosId, padId, idOffset - Numbers, special-token overrides. Defaults follow the detected tokenizer family (e.g. [CLS]/ [SEP] ids 101/102 for WordPiece).
    • lowercase, stripAccents, tokenizeChinese - Booleans, WordPiece tokenizer settings (see onnx.wordPieceTokenizer). Default: true.
Note:
The rampart-sql embed(), chunkembed() and related SQL functions can run this same engine inside the SQL module: sql.set({onnxEmbed: {model: '/path/to/modeldir'}}) loads the model through rampart-onnx. When embedding rows of a SQL table, that path avoids round-trips through JavaScript — see Generating embeddings, Chunked documents and the onnxEmbed property.
Return Value:
An Object (the embedding handle) with the functions oemb.embedTextToFp16Buf(), embedTextToFp32Buf(), embedTextToNumbers(), oemb.embedTextsToNumbers() and destroy(), plus session (the underlying onnx.initSession handle).

Example:

var onnx = require("rampart-onnx");

var oemb = onnx.initEmbed("./all-MiniLM-L6-v2");

var v = oemb.embedTextToFp16Buf("about a paragraph of text ...");

/* v = { vecs: [vec1, ...], avgVec: avg, coherence: n,
         chunks: [ {start,end,tokens,text}, ... ] }      */

/* store the whole-document vector in a sql table */
sql.exec("insert into vecs values(?,?,?)",
         [v.avgVec, docId, text]);

oemb.destroy();

oemb.embedTextToFp16Buf()

Embed text and return the vector(s) packed as 16-bit floats (little-endian) in Buffers, directly usable by rampart.vector functions, the rampart-sql vecdist() function and the faiss idx.addFp16() / idx.searchFp16() functions.

Usage:

var ret = oemb.embedTextToFp16Buf(text[, isQuery]);

Where:

  • text is a String, the text to be embedded.
  • isQuery is an optional Boolean. If true, the queryPrefix (if configured) is applied; if false or omitted, the passagePrefix (if configured) is applied.

If the tokenized text does not fit the model’s token window, it is chunked; with the default split: "auto" the chunking is structure-aware: text is split on paragraph boundaries (falling back to token windows for oversized paragraphs), so each vector corresponds to a semantically meaningful span of the input, and the spans are reported in chunks. rampart-llamacpp’s emb.embedTextToFp16Buf() chunks the same way — the two modules share the chunker.

Return Value:

An Object with the following properties:

  • vecs - An Array of Buffers, one vector per chunk (2 * dimension bytes each).
  • avgVec - A Buffer. If only one chunk was produced, the same vector as vecs[0]; otherwise the re-normalized average of the chunk vectors, suitable as a single whole-document vector.
  • coherence - A Number in [0, 1]: the average pairwise cosine similarity of the chunk vectors (1.0 for a single-chunk document). A low value means the document spans several topics and avgVec is a blurrier summary of it.
  • chunks - An Array of Objects, one per vector:
    • start, end - Numbers, the chunk’s byte span in text.
    • tokens - A Number, the chunk’s token count.
    • text - A String, the chunk text itself.
    • oversized - Boolean true when this vector is one of several token windows over a single span that exceeded the model window (such sub-chunks share their span). Not set otherwise.

For empty input, the return value is {vecs: []}.

oemb.embedTextToFp32Buf() / oemb.embedTextToNumbers()

The same as oemb.embedTextToFp16Buf() except that each vector is packed as 32-bit floats (4 * dimension bytes), or returned as an Array of Numbers, respectively.

Usage:

var ret = oemb.embedTextToFp32Buf(text[, isQuery]);
var ret = oemb.embedTextToNumbers(text[, isQuery]);

oemb.embedTextsToNumbers()

Embed several short texts in one batched model run — the fast path for many small inputs (e.g. embedding a list of queries or titles). Each text produces exactly one vector; text longer than the token window is truncated, not chunked.

Usage:

var ret = oemb.embedTextsToNumbers(texts[, isQuery]);

Where texts is an Array of Strings and isQuery selects the prefix as in oemb.embedTextToFp16Buf().

Return Value:
An Array (same order as texts) of Objects, each with a single property avgVec: the text’s vector as an Array of Numbers.

oemb.destroy()

Free the model session. Using the handle after calling destroy() throws an error.

Usage:

oemb.destroy();

onnx.initRerank

The initRerank function loads an ONNX cross-encoder reranking model (such as bge-reranker-v2-m3 or ms-marco-MiniLM-L6-v2) and returns a handle used to score how well documents answer a query.

Usage:

var onnx = require("rampart-onnx");

var orr = onnx.initRerank(modelPath[, options]);

Where:

  • modelPath is a String: a HuggingFace-layout model directory (recommended; the model, tokenizer, specials, token window and pair template are discovered) or a .onnx file path. For a file path the tokenizer is still discovered beside the model (the file’s directory, and its parent when the file is in an onnx/ subdirectory), so naming a specific .onnx variant works without options.tokenizer; pass it explicitly only if discovery fails.
  • options is an optional Object accepting the session options of onnx.initSession and the tokenizer / special-token options of onnx.initEmbed, plus:
    • pairTemplate - A String, "bert" ([CLS] query [SEP] document [SEP] with token types) or "roberta" ([bos] query [eos eos] document [eos]). Default: "bert" when the tokenizer is WordPiece, else "roberta". Instruct-style rerankers (the Qwen3-Reranker family) are not supported here — use the rampart-llamacpp initRerank for those.
    • sigmoid - A Boolean. If true (the default), scores are passed through a sigmoid and lie in (0, 1); if false, raw model logits are returned (matching rampart-llamacpp’s rr.rerank()).
Return Value:
An Object (the reranker handle) with the functions orr.rerank() and destroy(), plus session.
Note:
The handle carries all of its state as object properties with native methods, so it survives being shared with other rampart threads — including a rampart-server preThreadFunc global propagating to worker threads, like a rampart-llamacpp handle. Handles from onnx.initEmbed and initSnacDecoder are thread-portable the same way.

orr.rerank()

Score one or more documents against a query. All documents are scored in one batched model run.

Usage:

var score  = orr.rerank(query, document);
var scored = orr.rerank(query, documents[, scoresOnly]);

Where query is a String and document / documents is a String or an Array of Strings, as in the llamacpp rr.rerank().

Return Value:
  • Given a single String document: a Number, the relevance score.
  • Given an Array of documents: an Array of Objects, each {document: String, score: Number, index: Number}, sorted best-first; index is the document’s position in the input Array. (Note this differs from the llamacpp reranker, which returns results in input order.)
  • Given an Array and scoresOnly = true: an Array of Numbers in input order (llamacpp parity).

Example:

var onnx = require("rampart-onnx");

var orr = onnx.initRerank("./ms-marco-MiniLM-L6-v2");

var scored = orr.rerank("How tall is the Eiffel Tower?", [
    "The Eiffel Tower is 330 metres tall.",
    "Gustave Eiffel also designed bridges.",
    "Paris is the capital of France."
]);
/* [ { document: "The Eiffel Tower is ...", score: 0.98, index: 0 },
     { document: "Paris is the capital ...", score: 0.03, index: 2 },
     ...  -- sorted best-first                                     ] */

orr.destroy();

onnx.initSession

The initSession function loads any .onnx model and returns a low-level session handle: named input tensors go in, named output tensors come out. This is the layer beneath onnx.initEmbed / onnx.initRerank, exposed for models that are neither embedders nor rerankers (classifiers, taggers, audio and vision models, …).

Usage:

var onnx = require("rampart-onnx");

var sess = onnx.initSession(path[, options]);

Where:

  • path is a String, the path of a .onnx model file.
  • options is an optional Object with the following properties:
    • gpu - A Boolean. Whether to run the session on the GPU via the CUDA execution provider (see CPU and GPU (runtime selection)). Default: autotrue when the module selected a GPU runtime (a GPU build with a usable device), otherwise false. Pass gpu: false to force CPU even on a GPU build. A GPU session that cannot be created falls back to CPU with a one-line notice rather than throwing. provider: "cuda" / "cpu" is accepted as an alternative spelling (provider: "cpu" also forces CPU).
    • device - A Number, the CUDA device id (with gpu: true). Default: 0.
    • intraOpThreads - A Number, threads used within an operator. Default: 0 = ONNX Runtime’s own thread pool (sized to the machine’s cores), so a single call uses the full CPU. Set 1 for a session with no background threads — see the fork note below.
    • interOpThreads - A Number, threads used to run independent operators concurrently (only meaningful with executionMode: "parallel"). Default: 1.
    • executionMode - A String, "sequential" (default) or "parallel".
    • graphOpt - A String, the graph optimization level: "disable", "basic", "extended" or "all". Default: ONNX Runtime’s default ("all").
Note:
Fork safety costs no performance. A session whose thread pool is broken by a fork() is transparently rebuilt from its model source on first use in the child process; a GPU session (whose device context cannot be rebuilt in a forked child) throws a descriptive error there instead. intraOpThreads: 1 (with sequential execution) creates a session with no background threads at all, which a forked child can keep using without any rebuild. Concurrency is also available across rampart threads: a session may be used from several threads at once (run is thread-safe).
Return Value:
An Object (the session handle) with the functions sess.run(), sess.inputs(), outputs(), sess.metadata() and destroy().

Example:

var onnx = require("rampart-onnx");

var sess = onnx.initSession("model.onnx");

/* what does it want? */
var ins  = sess.inputs();
/* [ { name: "input_ids",      type: "int64", shape: [-1,-1] },
     { name: "attention_mask", type: "int64", shape: [-1,-1] } ] */

var out = sess.run({
    input_ids:      { data: [101, 7592, 102], shape: [1, 3], type: "int64" },
    attention_mask: { data: [1, 1, 1],        shape: [1, 3], type: "int64" }
});

/* out.last_hidden_state.array is a Float32Array,
   out.last_hidden_state.shape e.g. [1, 3, 384] */

sess.destroy();

sess.run()

Run the model once.

Usage:

var outputs = sess.run(feeds);

Where feeds is an Object mapping each input’s name to a tensor Object:

  • data - A Buffer (raw little-endian element bytes) or an Array of Numbers (converted to the given type).
  • shape - An Array of Numbers, the tensor dimensions. If omitted, the tensor is treated as 1-D.
  • type - A String, the element type: "float32", "float16", "double", "int64", "int32", "int16", "int8", "uint8" or "bool".
Return Value:

An Object mapping each output’s name to a tensor Object:

  • data - An ArrayBuffer of the raw element bytes (so e.g. new Float32Array(t.data) reinterprets rather than copies).
  • array - A ready-made typed-array view over data matching the element type (Float32Array, Int32Array, …). Omitted for int64 outputs, which have no native typed array (use data).
  • shape - An Array of Numbers.
  • type - A String, the element type.

sess.inputs()

Return the model’s declared inputs (sess.outputs() likewise returns its outputs): an Array of Objects, each {name: String, type: String, shape: Array}. Dynamic dimensions appear as -1.

Usage:

var ins  = sess.inputs();
var outs = sess.outputs();

sess.metadata()

Return the model’s metadata: an Object with producerName, graphName, domain, description (Strings, present when set in the model) and version (a Number).

Usage:

var meta = sess.metadata();

sess.destroy()

Free the session. Using the handle after calling destroy() throws an error. Sessions are also freed automatically when garbage collected.

Usage:

sess.destroy();

onnx.initSessionFromBuffer

The same as onnx.initSession except that the model is read from a Buffer of model bytes rather than a file.

Usage:

var sess = onnx.initSessionFromBuffer(buffer[, options]);

onnx.modelInfo

Return a model’s declared inputs and outputs without creating a full session.

Usage:

var onnx = require("rampart-onnx");

var info = onnx.modelInfo(path);

Where path is a String, the path of a .onnx model file.

Return Value:
An Object with inputs and outputs, each an Array of {name, type, shape} Objects as returned by sess.inputs().

onnx.wordPieceTokenizer

Create a WordPiece (BERT-style) tokenizer from a vocab.txt file. onnx.initEmbed / onnx.initRerank create one of these automatically when the model directory contains a *vocab.txt; the function is exposed for custom pipelines.

Usage:

var onnx = require("rampart-onnx");

var tok = onnx.wordPieceTokenizer(vocabPath[, options]);

Where:

  • vocabPath is a String, the path of the vocab.txt file.
  • options is an optional Object with the Boolean properties lowercase, stripAccents and tokenizeChinese, each defaulting to true.
Return Value:
An Object with the property vocabSize (a Number) and the function encodeIds(text), which returns an Array of Numbers — the content token ids of text, without special tokens ([CLS]/[SEP] are added by the embed/rerank layers).

onnx.spTokenizer

Create a SentencePiece/BPE tokenizer from a HuggingFace tokenizer.json. As with onnx.wordPieceTokenizer, this is created automatically for model directories that have a tokenizer.json.

Usage:

var onnx = require("rampart-onnx");

var tok = onnx.spTokenizer(modelDir);

Where modelDir is a String, the directory containing tokenizer.json.

Return Value:
An Object with the function encodeIds(text), as in onnx.wordPieceTokenizer.

onnx.initSnacDecoder

Experimental. Load a SNAC audio-codec decoder model and return a handle that turns SNAC codes (as produced by speech models such as Orpheus TTS) into 24 kHz audio samples. The handle has the property sampleRate (24000) and the functions decode(codes), framesToCodes(frames), decodeFrames(frames), decodeOrpheus(tokens) and destroy(); decoded audio is returned as a Float32Array of samples. The API may change.

onnx.onnxVersion

Return the ONNX Runtime version string (e.g. "1.27.0").

Usage:

var v = onnx.onnxVersion();

onnx.runtimeInfo

Return a String describing which runtime the selection ladder picked (see CPU and GPU (runtime selection)) — e.g. "built-in CPU", or the CUDA runtime directory with the driver version and GPU compute capability.

Usage:

rampart.utils.printf("%s\n", onnx.runtimeInfo());
/* "/usr/local/rampart/modules/onnx-cu12 (driver CUDA 12.2, sm 89)" */

onnx.getLog / onnx.clearLog

ONNX Runtime warnings and non-fatal errors are captured in an internal buffer rather than printed to stderr. getLog() returns the captured log as a String; clearLog() empties it (resetLog() is an alias, for rampart-llamacpp naming parity).

Usage:

var log = onnx.getLog();
onnx.clearLog();

Converting a model to float16 or int8

ONNX embedding and reranking models are usually exported in float32. For GPU inference a float16 copy roughly halves the weight size and uses the GPU’s tensor cores — usually the fastest ONNX option (the module auto-uses the GPU; see CPU and GPU (runtime selection)). An int8 (dynamically quantized) copy is smaller still but is CPU-oriented: ONNX Runtime’s CUDA execution provider has sparse int8 kernel coverage, so int8 tends to fall back to the CPU (with CPU⇄GPU copies) and is usually slower on the GPU than fp16 — prefer it with gpu: false.

Conversion is a one-time, offline step using Python’s ONNX tooling (not a runtime rampart operation): convert once and place the resulting .onnx beside the original, then load it like any other model. Install the tooling into a throwaway virtualenv matching your python3:

python3 -m venv /tmp/onnxconv
/tmp/onnxconv/bin/pip install onnxruntime onnx sympy

float16 — use ONNX Runtime’s transformer optimizer, which inserts/repairs the Cast nodes that a bare onnxconverter_common pass gets wrong. opt_level=0 applies no graph fusions (only the precision change, so outputs do not drift); keep_io_types leaves the int/float inputs and outputs unchanged; and use_external_data_format handles weights that exceed protobuf’s 2 GB single-file limit:

from onnxruntime.transformers.optimizer import optimize_model
m = optimize_model("model.onnx", model_type="bert",
                   opt_level=0, use_gpu=False)
m.convert_float_to_float16(keep_io_types=True)
m.save_model_to_file("model_fp16.onnx", use_external_data_format=True)

int8 — weight-only dynamic quantization (no calibration data needed):

from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic("model.onnx", "model_int8.onnx",
                 weight_type=QuantType.QInt8)

Then load the result as usual, e.g. onnx.initRerank("<dir>/onnx/model_fp16.onnx", {tokenizer: ...}). A .onnx saved with external data keeps its weights in a sidecar file (model_fp16.onnx.data); keep the two together.

The rampart-clip module

Experimental. rampart-clip is new in this release. It is under active development and its API may change.

Loading the module is a simple matter of using the require() function:

var clip = require("rampart-clip");

CLIP (Contrastive Language–Image Pre-training) maps images and text into a single shared vector space, so similar concepts land close together regardless of modality. That makes it possible to search a collection of images with a text query, find images similar to a reference image, or score how well a caption matches a picture — all with plain dot-product similarity on the vectors it returns.

The module is built on a CLIP implementation ported onto the same ggml tensor library rampart-llamacpp uses, so it links no second copy of ggml and — like the other langtools modules — a loaded model is shared across rampart threads: every thread copy of a model object shares one read-only set of weights and does its own computation, so a model may be handed to a worker thread and used there directly (see CLIP and threads).

Models

rampart-clip loads CLIP models in the GGUF format used by clip.cpp — standard OpenAI and LAION CLIP models converted to GGUF, available pre-converted on HuggingFace (e.g. the mys/ggml_CLIP-ViT-* repositories). The q8_0 quantization is a good default: nearly identical to f16 in quality at roughly half the size.

The The rampart-models module helper can fetch one into the standard location for you:

var models = require("rampart-models");
var path   = models.get("clip-vit-b-32-laion:q8_0");   // -> local .gguf path

clip.initEmbed

clip.initEmbed(modelPath[, options]) (alias: clip.load)

Load a CLIP GGUF model and return a model object carrying the image+text embedding methods below. modelPath is the path to a .gguf file (typically from The rampart-models module). Named initEmbed to match the other langtools modules (initEmbed/initGen/onnx.initEmbed); clip.load is a backward-compatible alias for the same call.

The optional options object accepts:

  • nThreadsNumber. CPU threads for inference.

A given model file is loaded once per process and shared: calling clip.initEmbed again with the same path (on any thread) returns a handle onto the already-resident weights rather than reloading them.

var model = clip.initEmbed(path);
model.dimension;            // 512 for ViT-B/32, 768 for ViT-L/14, ...

model.dimension

Read-only Number: the dimension of the shared embedding space (the length of every vector this model produces).

model.onGpu

Read-only Boolean: true if this model’s weights and compute live on a GPU (CUDA, or Metal on macOS), false if it runs on the CPU — a CPU-only build, no GPU present, or a GPU that was rejected (see CLIP and the GPU, and clip.errMsg for the reason). Fixed when the model is loaded: thread copies compute on the same backend, so the value cannot go stale.

model.embedImageToNumbers

model.embedImageToNumbers(image)

Embed an image and return the vector as a JavaScript Array of Number. image is either a String file path or a Buffer holding the image bytes — a JPEG/PNG/… blob such as a readFile() result, a fetch() body, or a rampart-sql varbyte column. A String is always taken as a path and a Buffer always as image bytes (never the reverse). Any buffer flavor is accepted (a plain buffer, Uint8Array, ArrayBuffer, or a Node Buffer). Either way the image is decoded, resized and center-cropped to the model’s input size automatically; supported formats include JPEG, PNG, BMP, GIF (first frame), TGA, PSD, HDR, PIC and PNM. The returned vector is L2-normalized. (Contrast the embedText* methods below, where a Buffer means text stored in a buffer, not an image.)

model.embedImageToFp16Buf / embedImageToFp32Buf

model.embedImageToFp16Buf(image) / model.embedImageToFp32Buf(image)

As above — image is a path String or a Buffer of image bytes — but return the vector as a Buffer of fp16 (2 bytes per element) or fp32 (4 bytes per element) values — the compact forms to store in a The rampart-faiss module index or a rampart-sql vector column.

model.embedTextToNumbers / embedTextToFp16Buf / embedTextToFp32Buf

model.embedTextToNumbers(text) (and the Fp16Buf / Fp32Buf variants)

Embed a text string into the same space as the image methods, so an image vector and a text vector can be compared directly. Return types match the image methods.

model.similarity

model.similarity(vec1, vec2)

Cosine similarity (a Number from -1 to 1) between two vectors, each an fp16 or fp32 Buffer as returned by the *Buf methods (both must be the same type). Because the vectors are L2-normalized this is simply their dot product; higher means more similar.

model.destroy

Release this model object. The object must not be used afterward (any method then throws). The underlying weights stay resident in the process for reuse, exactly as rampart-onnx and rampart-llamacpp keep embedding models loaded — so a subsequent clip.initEmbed of the same path is instant.

CLIP and the GPU

On a CUDA build the model is loaded into the GPU and embedded there; on macOS the GPU is Metal. The choice is automatic and needs no option, but it is made only when the device can actually run the model:

  • A CUDA device whose compute capability is not in this module’s build, or an NVIDIA driver older than the CUDA the module was built with, is rejected (the same guard the other langtools modules apply).
  • A GPU that does not implement every operation CLIP’s graphs need is rejected. This is what rules out Metal on older Macs: ggml’s Metal backend implements matrix multiply, normalization and softmax only on MTLGPUFamilyApple7 or Metal 3 devices — i.e. Apple Silicon, or macOS 13 and later — so an Intel Mac, or any Mac running macOS 11/12, has no usable Metal path here.

In either case the model quietly falls back to the CPU and the reason is placed in clip.errMsg (see Errors, Warnings and Logs). Results are identical either way; only speed differs. model.onGpu reports which one a loaded model got:

var model = clip.initEmbed(path);
if (!model.onGpu && clip.errMsg)
    printf("CLIP on CPU: %s\n", clip.errMsg);

Note that clip.getLog() is not a reliable indicator here: the op check has to create the GPU device in order to ask it what it supports, so the log contains the backend’s start-up banner even when the model then went to the CPU.

CLIP and threads

A model object is thread-safe to copy: it may be passed into a rampart.thread worker (or is copied automatically when a server hands a request to a worker) and used there. All copies share one read-only set of weights; each thread builds its own small compute context on first use. This is the recommended way to embed in parallel — the heavy weights are not duplicated.

Warnings and errors follow the langtools convention described in Errors, Warnings and Logs: a failure throws a JavaScript Error, a non-fatal problem is placed in errMsg, and nothing is written to stdout or stderr.

Example: search images by text

var clip   = require("rampart-clip");
var models = require("rampart-models");
var printf = rampart.utils.printf;

var model = clip.initEmbed(models.get("clip-vit-b-32-laion:q8_0"));

// embed a set of images once
var images = ["cat.jpg", "dog.jpg", "beach.jpg"].map(function(f) {
    return { name: f, vec: model.embedImageToFp32Buf(f) };
});

// rank them against a text query
var q = model.embedTextToFp32Buf("a dog playing outside");
images
    .map(function(im) { return { name: im.name, score: model.similarity(im.vec, q) }; })
    .sort(function(a, b) { return b.score - a.score; })
    .forEach(function(r) { printf("  %-12s %.4f\n", r.name, r.score); });

model.destroy();

The rampart-faiss module

Loading the module is a simple matter of using the require() function:

var faiss = require("rampart-faiss");

Note that the rampart-sql module’s built-in Vector Indexes (the IVFPQ backend of CREATE VECTOR INDEX) are also FAISS indexes, maintained automatically as table rows change. Use rampart-faiss directly when the vectors do not live in a SQL table, or when a custom index type, metric or training regime is needed.

openFactory

The openFactory function creates a new (empty) vector index from a FAISS index factory description string. See also the FAISS guidelines for choosing an index.

Usage:

var faiss = require("rampart-faiss");

var idx = faiss.openFactory(description, dimensions[, metricType]);

Where:

  • description is a String, the factory description (e.g. "Flat", "IDMap2,Flat", "IDMap2,OPQ96,IVF262144,PQ48"). It is highly recommended to include IDMap or IDMap2 so that arbitrary ids can be stored with each vector; otherwise ids are assigned sequentially starting at 0.
  • dimensions is a positive Number, the vector dimension (e.g. 384 for all-minilm-l6-v2 embeddings — see modelInfo to read it from the embedding model).
  • metricType is an optional String, the distance metric (case-insensitive):
    • "innerProduct" or "ip" - inner (dot) product (the default). Equivalent to cosine similarity when vectors are L2-normalized, as the initEmbed vectors are.
    • "l2" - squared euclidean distance.
    • "l1", "manhattan" or "cityBlock" - L1 distance.
    • "linf" or "infinity" - L-infinity distance.
    • "lp" - Lp distance.
    • "canberra", "brayCurtis", "jensenShannon" - additional metrics supported by FAISS.
Return Value:
An Object (the index handle) with the property idx.settings and the functions idx.addFp32(), idx.addFp16(), idx.searchFp32(), idx.searchFp16(), idx.save() and (on CUDA builds) idx.enableGpu(). If the index type requires training, the handle additionally has the idx.trainer() function; idx.trainer is undefined for index types that need no training (such as Flat).

Example:

var faiss = require("rampart-faiss");

/* a small exact-search index storing our own doc ids */
var idx = faiss.openFactory("IDMap2,Flat", 4);

/* add fp32 vectors (Buffer of 4 floats = 16 bytes each) */
idx.addFp32(101, new Float32Array([1,0,0,0]).buffer);
idx.addFp32(102, new Float32Array([0,1,0,0]).buffer);

/* search: top 2 results for a query vector */
var res = idx.searchFp32(new Float32Array([0.9,0.1,0,0]).buffer, 2);
/* res = [ { id: 101, distance: 0.8999999761581421 },
           { id: 102, distance: 0.10000000149011612 } ]
   (distances come back as 32-bit floats widened to JavaScript
   doubles, so they carry fp32 rounding) */

idx.save("myindex.faiss");

openIndexFromFile

The openIndexFromFile function loads an index previously written with idx.save().

Usage:

var faiss = require("rampart-faiss");

var idx = faiss.openIndexFromFile(filename[, readOnly]);

Where:

  • filename is a String, the path of the saved index.
  • readOnly is an optional Boolean. If true, the index is opened read-only and memory-mapped, serving searches directly from disk rather than loading the entire index into RAM. Default: false.
Return Value:
An index handle, as returned by openFactory.

Example:

var faiss = require("rampart-faiss");
var llamacpp = require("rampart-llamacpp");

var idx = faiss.openIndexFromFile("myindex.faiss", true);
var emb = llamacpp.initEmbed("all-minilm-l6-v2_f16.gguf");

var v = emb.embedTextToFp16Buf("my search query");
var res = idx.searchFp16(v.avgVec, 10);

res.forEach(function(r) {
    rampart.utils.printf("id=%s distance=%f\n", r.id, r.distance);
});

idx.settings

A read-only Object describing the open index:

  • dimension - A Number, the vector dimension.
  • count - A Number, the number of vectors currently in the index (updated by idx.addFp32() / idx.addFp16()).
  • metricType - A String, the distance metric (see openFactory).
  • type - A String, the detected index type (e.g. "Flat", "IVFFlat", "IVFPQ", "HNSWFlat", "LSH").
  • map - A String, "IDMap" or "IDMap2" if the index stores arbitrary ids. Not set otherwise.
  • PQm, PQbits - Numbers, product-quantization parameters. Only set for PQ-based index types.
  • onGpu, gpuDevice - set after a successful idx.enableGpu() call.

idx.addFp32()

Add one vector of 32-bit floats to the index.

Usage:

var id = idx.addFp32(id, buffer);

Where:

  • id is a Number or String (for ids larger than the float precision of a Number), the 64-bit id to associate with the vector. Pass -1 to have an id assigned sequentially (0, 1, 2, …).

    The two forms are mutually exclusive, and which one applies is decided by the index type:

    • An IDMap/IDMap2 index stores your own ids, and so requires an explicit id; passing -1 throws.
    • Any other index type (e.g. a plain Flat) has no id map and so requires -1; passing an explicit id throws.

    In other words, use IDMap/IDMap2 when you need arbitrary ids, and a bare index when sequential ids are sufficient — see openFactory.

  • buffer is a Buffer of 4 * dimension bytes: the vector packed as little-endian 32-bit floats (e.g. from emb.embedTextToFp32Buf() or a Float32Array’s buffer).

If the index requires training (see idx.trainer()) it must be trained before vectors can be added.

Return Value:
A Number, the id under which the vector was stored (the passed id, or the assigned sequential id when -1 was passed).

idx.addFp16()

The same as idx.addFp32() except that buffer is 2 * dimension bytes: the vector packed as little-endian 16-bit (half-precision) floats, e.g. from emb.embedTextToFp16Buf() or rampart.vector conversion functions. The vector is converted to 32-bit floats before insertion (FAISS indexes store fp32).

Usage:

var id = idx.addFp16(id, buffer);

idx.searchFp32()

Search the index for the nearest vectors to a query vector of 32-bit floats.

Usage:

var results = idx.searchFp32(buffer[, nResults[, nProbe]]);

Where:

  • buffer is a Buffer of 4 * dimension bytes, the query vector (see idx.addFp32()).
  • nResults is an optional positive Number, the maximum number of results to return. Default: 10.
  • nProbe is an optional positive Number. For IVF index types, how many inverted-list cells to probe (more = better recall, slower search). Ignored for non-IVF indexes.
Return Value:

An Array of Objects, best match first:

  • id - A Number, the id stored with the vector.
  • distance - A Number, the metric value (for the default innerProduct metric, larger is more similar; for l2, smaller is more similar).

Fewer than nResults entries are returned if the index holds fewer vectors.

idx.searchFp16()

The same as idx.searchFp32() except that buffer is the query vector packed as 16-bit floats (2 * dimension bytes).

Usage:

var results = idx.searchFp16(buffer[, nResults[, nProbe]]);

idx.save()

Write the index to a file, which may later be loaded with openIndexFromFile. If the index has been moved to the GPU with idx.enableGpu(), it is converted back to a CPU index for saving (the in-memory index stays on the GPU).

Usage:

idx.save(filename);

Where filename is a String, the path of the file to write.

idx.enableGpu()

Move the index to a GPU. Only available on CUDA builds of the module; on CPU builds the function does not exist (check with if (idx.enableGpu)). Note that not every FAISS index type is supported on the GPU.

Usage:

if (idx.enableGpu)
    idx.enableGpu(device);

Where device is an optional Number, the GPU device id. Default: 0.

Return Value:
true upon success. After the call, idx.settings.onGpu is true and idx.settings.gpuDevice is set. An error is thrown on failure.

idx.trainer()

Index types that partition or compress the vector space (IVF, PQ, OPQ, LSH and similar) must be trained on a representative sample of vectors before any can be added. For such indexes the handle returned by openFactory includes a trainer function; for index types that need no training (e.g. Flat) idx.trainer is undefined.

The trainer accumulates training vectors in a file, so that millions of training vectors need not be held in memory, and so that an interrupted run can be resumed from the same file.

Usage:

if (idx.trainer) {
    var trainer = new idx.trainer(path);
    ...
}

Where path is an optional String:

  • A directory: a new training file named faisstrainingdata.<n>.<pid> is created there. Default: "/tmp".
  • An existing training file (from a previous run): its vectors are reloaded and trainer.settings.loadedRows is set to the number of vectors found. Newly added vectors are appended.
  • A non-existent path: it is created as a new (empty) training file.

The training file is not deleted automatically; it may be reused by a later run, or removed with rampart.utils.rmFile when no longer wanted.

Return Value:
An Object (the trainer handle) with the read-only property trainFile (a String, the path of the training data file) and the functions trainer.addTrainingfp32(), trainer.addTrainingfp16() and trainer.train().

Example:

var faiss = require("rampart-faiss");

/* an IVF index over 384-dim vectors: requires training */
var idx = faiss.openFactory("IDMap2,IVF4096,Flat", 384);

if (idx.trainer) {
    var trainer = new idx.trainer("./tdata");

    /* feed a representative sample of vectors */
    sql.exec("select Vec from vecs", {maxRows: 1000000},
        function(row) {
            trainer.addTrainingfp16(row.Vec);
        });

    trainer.train();   /* may take a while */
}

/* now vectors may be added */
sql.exec("select Id, Vec from vecs", {maxRows: -1},
    function(row) {
        idx.addFp16(row.Id, row.Vec);
    });

idx.save("vecs-ivf4096.faiss");

trainer.addTrainingfp32()

Append one 32-bit float vector to the training file.

Usage:

trainer.addTrainingfp32(buffer);

Where buffer is a Buffer of 4 * dimension bytes (see idx.addFp32()).

trainer.addTrainingfp16()

Append one 16-bit float vector to the training file. The vector is converted to 32-bit floats before being written.

Usage:

trainer.addTrainingfp16(buffer);

Where buffer is a Buffer of 2 * dimension bytes (see idx.addFp16()).

trainer.train()

Train the index from all the vectors accumulated in the training file (including vectors reloaded from a previous run). Depending on the index type and the number of training vectors, training can take from seconds to many hours.

Usage:

trainer.train();

An error is thrown if no vectors have been added, or if the training file’s size is not a multiple of the vector size.

The rampart-sentencepiece module

Loading the module is a simple matter of using the require() function:

var sp = require("rampart-sentencepiece");

init

The init function loads a SentencePiece model file and returns a handle for encoding text into subword pieces.

Usage:

var sp = require("rampart-sentencepiece");

var encoder = sp.init(path);

Where path is a String, the path to a SentencePiece model (e.g. sentencepiece.bpe.model from the bge-m3 repository).

Return Value:
An Object (the encoder handle) with the function encoder.encode().

Example:

var sp = require("rampart-sentencepiece");

var encoder = sp.init("./sentencepiece.bpe.model");

var pieces = encoder.encode("hello there you goat");
/* [ "▁hell", "o", "▁there", "▁you", "▁go", "at" ] */

var text = sp.decode(pieces);
/* "hello there you goat" */

encoder.encode()

Encode text into subword pieces using the loaded model. In the pieces, the character (U+2581, “lower one eighth block”) marks the start of a word.

Usage:

var pieces = encoder.encode(text[, asString]);

Where:

  • text is a String, the text to encode.
  • asString is an optional Boolean. If true, the pieces are returned as a single space-separated String instead of an Array. Default: false.
Return Value:
An Array of Strings (one per piece), or a single space-separated String if asString is true. Either form is accepted by decode.

decode

Reassemble encoded pieces into text. Decoding is purely textual (pieces are concatenated and the word-start markers become spaces), so no model handle is needed and the function lives on the module object itself.

Usage:

var sp = require("rampart-sentencepiece");

var text = sp.decode(pieces);

Where pieces is an Array of Strings, or a space-separated String of pieces, as produced by encoder.encode().

Return Value:
A String, the decoded text.

The rampart-models module

Experimental. rampart-models is new in this release. It is under active development and its API may change.

Loading the module is a simple matter of using the require() function:

var models = require("rampart-models");

The module downloads and locates GGUF and ONNX models by short name, returning a local path that feeds initEmbed, initRerank, initGen, onnx.initEmbed / onnx.initRerank and the rampart-sql llamaEmbed / onnxEmbed properties directly:

var models   = require("rampart-models");
var onnx     = require("rampart-onnx");
var llamacpp = require("rampart-llamacpp");

var oemb = onnx.initEmbed( models.get("bge-m3") );          // onnx .onnx file
var emb  = llamacpp.initEmbed( models.get("bge-m3:q8_0") ); // gguf FILE
var gen  = llamacpp.initGen( models.get("qwen3-4b") );      // gen = gguf

Models live under ~/.rampart/models/<category>/ (categories: embed, rerank, gen, clip; plain URLs go to other). If the model is already on disk its path is returned immediately with no network access; otherwise it is downloaded from HuggingFace with resume, retries and a single-line progress display.

A short name is resolved in this order:

  1. Already on disk under ~/.rampart/models/.
  2. The embedded catalog — currently 81 curated models (embedding, reranking, text-generation and CLIP), each pinned to a specific repository revision. Embedding entries also record the model’s vector dimension and its retrieval prompts, when it has them (see Retrieval prompt sidecars below). models.list() (or --list on the command line) shows them; it returns an Object keyed by category, each value an Array of the model names in that category.
  3. A name containing / is used as an exact HuggingFace org/repo — no search.
  4. A live HuggingFace search (exact-name match first, model-family organizations before converter organizations, then quant coverage). Live resolutions are remembered in ~/.rampart/models/.resolved.json so the same name resolves the same way next time.

models.get()

Resolve (and, if needed, download) a model; return its local path.

Usage:

var path = models.get(name[, options]);

Where:

  • name is a String: a catalog short name ("bge-m3"), a short name with a quant suffix ("bge-m3:q8_0" — implies GGUF), an exact HuggingFace "org/repo", or a full https:// URL (downloaded as-is).
  • options is an optional Object:
    • format - A String, "onnx" or "gguf". Default: "onnx" when the model has an ONNX form and is an embedding/reranking model, else "gguf" (text-generation models are GGUF-only).
    • quant - A String, the GGUF quantization (e.g. "Q4_K_M", "Q8_0", "F16"); same meaning as the :quant name suffix. When the exact quant isn’t available, the closest available one is chosen.
    • precision - A String, the ONNX weight precision: "fp16" (the default — half precision, the GPU sweet spot), "fp32" (full precision, the reference), "int8" or "q4" (quantized — smaller and often faster on CPU). Since model authors rarely publish every precision in the original repository, the closest converter mirror (onnx-community/*, Xenova/*) is searched as well; if the requested precision isn’t found anywhere it falls back to fp16 then fp32, printing a notice to stderr. Ignored for GGUF (use quant there).
    • category - A String, the subdirectory under ~/.rampart/models/ (default: from the catalog, else "embed"; URLs default to "other").
    • dest - A String, an exact destination file or directory, overriding the category layout.
    • progress - false (silent), a file handle to write progress to, or a Function called with progress info. Default: single-line progress on stdout.
    • force - A Boolean, re-download even if the model is already present. Default: false.
    • token - A String, a HuggingFace access token for gated repositories. Default: the HF_TOKEN environment variable.
    • confirm - A Function, called only when a download is actually needed (the model isn’t already on disk), so the caller can prompt before a large fetch. It receives an info Object ({name, format, dest, size, bytes, precision|quant, repo}) and returns a Boolean; a falsy return skips the download and models.get() returns null. Omit it for the default silent fetch. The module never reads stdin itself — any prompt lives in this callback.
    • revision - A String, the git revision to fetch. Default: the catalog-pinned revision, else "main".
Return Value:
A String: the local path — the .onnx file for ONNX models, a .gguf file for GGUF. Throws on resolution or download failure. Returns null when a confirm callback declines a needed download.

models.pull() is an alias of models.get().

Note:
For an ONNX model the whole usable directory is fetched — the .onnx file (plus its .onnx_data weights sidecar when present) alongside the tokenizer and configuration files (tokenizer.json / vocab.txt, config.json, 1_Pooling/ etc.) — but the returned path is the .onnx file itself. onnx.initEmbed / onnx.initRerank accept that file directly and auto-discover the tokenizer and configuration from its directory, so it feeds them (and the onnxEmbed property) as-is.

models.ggufGet() / models.onnxGet()

Format-explicit variants of models.get() — the call site then reads as the engine it feeds:

var emb  = llamacpp.initEmbed( models.ggufGet("bge-m3") );
var oemb = onnx.initEmbed(     models.onnxGet("bge-m3") );

models.url()

Download a plain URL into the models directory (or dest) with the same resume/retry/progress machinery; returns the file path.

Usage:

var path = models.url(theUrl[, options]);

models.resolve()

Resolve a name to its catalog-shaped entry — repository, pinned revision, available quants, license, category, and (for embedding models) vector dimension and retrieval prompts — without downloading.

Usage:

var entry = models.resolve(name[, options]);

models.list()

Return the embedded catalog’s short names grouped by category (an Object of Arrays), each annotated with its available formats:

var l = models.list();
/* { embed:  [ "all-minilm-l6-v2 [onnx+gguf]", "bge-m3 [onnx+gguf]", ... ],
     rerank: [ "ms-marco-minilm-l6-v2 [onnx+gguf]", ... ],
     gen:    [ "qwen3-4b [gguf]", ... ] }                                  */

The module also exposes models.catalog (the raw catalog Object) and models.modelsDir (the ~/.rampart/models path).

Retrieval prompt sidecars

Many embedding models are asymmetric: they expect a short prefix on queries, on documents, or on both (e.g. nomic-embed-text-v1.5’s "search_query: " / "search_document: ", or the e5 family’s "query: " / "passage: "). Using such a model without its prompts costs retrieval quality.

The catalog records each model’s published prompts, and every download writes them into a small sidecar file next to the model: <file>.gguf.prompts.json beside a GGUF file, and <name>.prompts.json beside an ONNX model directory. Fetching the path of an already-downloaded model refreshes the sidecar, so models downloaded before this feature gain one on their next models.get().

The rampart-sql llamaEmbed and onnxEmbed settings read the sidecar automatically — likev queries, chunkembed() and embed(?, 'query'|'document') then apply the right prompt with no further configuration. See Retrieval prompts for how the prompts are applied and how to override or disable them. Symmetric models (e.g. all-minilm-l6-v2) have no prompts and embed text verbatim.

The module-level engines embed exactly the text they are given: when calling initEmbed / onnx.initEmbed directly, prepending a model’s prompts is the caller’s responsibility.

Command line

The module doubles as a downloader script:

rampart rampart-models.js bge-m3                # onnx .onnx file (fp16)
rampart rampart-models.js bge-m3 onnx fp32      # onnx, full precision
rampart rampart-models.js bge-m3 onnx q4        # onnx, 4-bit quantized
rampart rampart-models.js bge-m3 gguf Q8_0      # gguf file, chosen quant
rampart rampart-models.js qwen3-4b:q4_k_m       # quant suffix
rampart rampart-models.js --list                # show the catalog

The third argument is the ONNX precision (fp16 | fp32 | int8 | q4, default fp16) when the format is onnx, or the GGUF quant otherwise. The resolved local path is printed on success. --list groups the catalog by category, colorizes on a color terminal (plain when piped), and marks already-downloaded models — e.g. [installed (onnx fp32, gguf Q4_K_M)] — showing the on-disk precision/quant of each.

Environment: HF_TOKEN supplies the HuggingFace token for gated repositories; HF_ENDPOINT overrides the HuggingFace host (for mirrors). Only HuggingFace’s stable URL patterns are used (api/models, resolve/{revision}/), never CDN URLs.

Putting It Together

A compact end-to-end example: fetch a model, embed documents, index them, and serve semantic search queries. Runs as-is on a fresh install — models.get() downloads the model on first use and returns its local path immediately thereafter.

rampart.globalize(rampart.utils);

var models   = require("rampart-models");
var llamacpp = require("rampart-llamacpp");
var faiss    = require("rampart-faiss");

var mdl = models.ggufGet("all-minilm-l6-v2");

var emb = llamacpp.initEmbed(mdl);
var dim = llamacpp.modelInfo(mdl).embedDim;

var docs = [
    "The Eiffel Tower is 330 metres tall.",
    "Gustave Eiffel also designed bridges.",
    "Semantic search finds meaning, not just words."
];

/* build the index */
var idx = faiss.openFactory("IDMap2,Flat", dim);
docs.forEach(function(text, i) {
    var v = emb.embedTextToFp16Buf(text);
    idx.addFp16(i, v.avgVec);
});

/* search it */
var q   = emb.embedTextToFp16Buf("How high is the Eiffel Tower?");
var res = idx.searchFp16(q.avgVec, 2);

res.forEach(function(r) {
    printf("%f  %s\n", r.distance, docs[r.id]);
});

For a larger, trained index (tens of millions of vectors) see the idx.trainer() example above; for improving the final ordering of the top results, see initRerank.

Integration with rampart-sql

The rampart-sql module has this whole pipeline built in, so vectors can be generated, indexed and searched inside the SQL engine — the langtools modules are the engines under the hood, with no application glue required.

Embedding — the embed() SQL function. A value is turned into a vector by an embedding engine loaded once per connection with sql.set():

  • llamaEmbed runs a GGUF model through rampart-llamacpp (initEmbed) — the CPU / macOS default.
  • onnxEmbed runs an ONNX model through rampart-onnx (onnx.initEmbed) — GPU acceleration is available only on NVIDIA.
  • clipEmbed runs a CLIP model through rampart-clip (clip.initEmbed) — images and text in one shared space, so an image table can be searched by a text query (see below).

The text engines (llamaEmbed / onnxEmbed) use the structure-aware chunking and pooling described under Generating embeddings; all three share one loaded model across the SQL worker threads — the thread-copyable, refcounted handle design these modules use makes that safe.

Indexing and search — FAISS vector indexes. CREATE VECTOR INDEX builds a compressed IVFPQ index and the LIKEV operator searches it by vector similarity, both backed by rampart-faiss (The rampart-faiss module) inside the engine. See Vector Indexes and Vector Search. The typical flow embeds documents with embed(), stores the vectors in a column, indexes that column, and answers WHERE vec LIKEV 'a natural-language query'.

Cross-modal image search — clipEmbed. Because CLIP embeds images and text into the same space, an image table is searched by a plain text query. An image is stored with embed(?, 'image') — the bytes (a Buffer from a column, or a file path) go through CLIP’s vision encoder — and found with an ordinary WHERE Vec LIKEV 'a dog catching a frisbee', where the query text goes through CLIP’s text encoder. Image Buffer parameters are embedded in the calling process before the statement is bound, so a multi-megabyte blob is never shipped to the SQL helper and back. See clipEmbed for the details.

Reordering — reranking. For the highest-precision top results a cross-encoder reranker (initRerank / onnx.initRerank) can reorder a short candidate list after the vector search — a little more latency for a better final ordering.