Mean Pooling vs CLS: Watch Transformers.js Turn a Sentence Into an Embedding in Node A developer demonstrated in Node how Transformers.js's feature-extraction pipeline returns per-token tensors by default, with pooling set to "none" and normalize to false, using an ONNX export of all-MiniLM-L6-v2 via ONNX Runtime. The walkthrough shows the tokenizer's WordPiece splits, special tokens, and attention masks, and warns that mismatched pooling between indexing and query paths silently degrades retrieval quality. Somewhere in your RAG code there is a line like const vec = await embed text . A list of 384 floats comes back, you store it, and you never look inside again. That function hides a choice. In Transformers.js https://huggingface.co/docs/transformers.js/index , the feature-extraction pipeline has two options that decide what those floats mean, and both default to "do nothing": pooling defaults to "none" and normalize defaults to false , per the pipeline API docs https://huggingface.co/docs/transformers.js/api/pipelines . Call it with no options on "This is a simple test." and the docs show you get a tensor of shape 1, 8, 384 . You get eight vectors back, one per token. Every embedding library makes this choice for you somewhere. When the indexing job and the query path make it differently, nothing throws. Retrieval just gets quietly worse. Open the box in Node one step at a time, then break it on purpose. Install the library version 4.3.1 is the current release on npm https://registry.npmjs.org/@huggingface/transformers/latest , and in Node it runs ONNX models through ONNX Runtime https://onnxruntime.ai/ . Run the script with tsx https://github.com/privatenumber/tsx . The model downloads from the Hugging Face Hub https://huggingface.co/onnx-community/all-MiniLM-L6-v2-ONNX on first run. No API key. npm i @huggingface/transformers npx tsx embed.mts The model is an ONNX export of all-MiniLM-L6-v2 https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 , the same one the Transformers.js docs use. Its model card says it maps text to a 384-dimensional space, uses mean pooling, and truncates input longer than 256 word pieces. js // embed.mts import { AutoModel, AutoTokenizer, type Tensor, } from "@huggingface/transformers"; const ID = "onnx-community/all-MiniLM-L6-v2-ONNX"; const tokenizer = await AutoTokenizer.from pretrained ID ; const model = await AutoModel.from pretrained ID, { dtype: "fp32", } ; const query = "I can't log in. How do I change my password?"; const docs = "Reset your password from the account settings page.", "Our office is closed on public holidays.", "Two-factor codes expire after 30 seconds.", "Passwords must be at least 12 characters long.", "Contact billing to update your credit card.", ; const all = query, ...docs ; console.log tokenizer.tokenize query ; const enc = await tokenizer all, { padding: true, truncation: true, } ; console.log enc.input ids.tolist 0 ; console.log enc.attention mask.tolist 1 ; const out = await model enc as Record