Fast_Sentence_Embeddings

oborchersMIT活跃维护

用 Cython 加速 fastText 和 gensim 模型计算句向量,比原生 Python 实现快一个数量级,适合需要批量生成句向量做相似度检索的场景。

Compute Sentence Embeddings Fast!

v2.0.0发布于 2026-11-07 13:10 UTC639star +2 / 7 天85fork同步于 2026-11-10 17:57 UTC

怎么上手

由 README 提炼,执行前请确认
pip install "fse>=2.0"
硬件要求按 README 说明

纯 CPU 运行,无需 GPU;README 未说明具体 CPU 型号要求,基准测试使用 AMD EPYC 服务器。

纯 CPU 可运行支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

在 CPU 上高速计算静态句向量的推理引擎,无需 GPU 即可批量生成文本嵌入

用 Cython 加速 fastText 和 gensim 模型计算句向量,比原生 Python 实现快一个数量级,适合需要批量生成句向量做相似度检索的场景。

核心能力

  • 用多进程并行编码,16 个 worker 时吞吐量可达每秒数百万条
  • 支持 Model2Vec、静态 Sentence Transformers 模型和 GloVe、word2vec、FastText 词向量
  • 提供 float32、float16、int8 量化,可压缩模型体积并保存为 safetensors
  • 内置线性分类头,可在同一应用内训练并保存文本分类器
  • 支持文件断点续跑与队列、Qdrant 等应用场景的完整示例

容易踩的坑

仅支持静态嵌入模型,不能运行 MiniLM、BGE 等上下文 Transformer;首次加载会从 Hugging Face 下载模型,需注意模型许可证;使用 GloVe、word2vec、FastText 词向量需额外安装 fse[gensim] 适配器。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v2.0.0
  1. v2.0.0fse 2.0.0最新正式版2026-11-07 · 2026-11-07 13:10 UTC

    fse 2.0.0 is a rewrite. fse is now an inference engine for finished static embedding models: it turns text into vectors on the CPU and trains nothing. Load a Model2Vec model, a supported static Sentence Transformers model or existing word vectors, and encode. fse 1.x stays maintained for bug and compatibility fixes on the branch 1.x.

    Upgrading from 1.x

    • The Python API and the saved file format have changed. Average, SIF and uSIF with train and infer become an Encoder with encode. SIF and uSIF weights are fitted once offline and folded into the word rows. Models are saved as safetensors files.
    • Read Moving from fse 1.x to 2.0 for every replacement, with 1.x and 2.0 code side by side.
    • Requires Python 3.11 to 3.15. gensim is optional (fse[gensim]) and installs on Python 3.11 to 3.13.
    • License: MIT.

    Highlights

    • Speed. 100 million WikiText sentences embedded and written to disk in 13.46 seconds (float32 model table, int8 output, 16 workers, AMD EPYC 9575F with 32 vCPU, median of three runs). How we measured.
    • Your models. Encoder.from_pretrained loads Model2Vec models and static Sentence Transformers models from the Hugging Face Hub or a local folder, and 30 classic word-vector models are on the Hub as fse encoders.
    • Smaller models. Encoder.quantize("float16") or ("int8") shrinks the model table.
    • Write and search vectors on disk. ShardSink writes vectors in shards while encoding; VectorStore reads and searches them.
    • Label text. Bring a trained linear classifier and fold it into the model to label text in the same application.
    • Recipes as notebooks for search, classification and more, and pre-built wheels for Linux, macOS and Windows.

    The full list of changes is in CHANGELOG.md. The packages are on PyPI.

  2. v1.1.0fse 1.1.02026-10-27 · 2026-10-27 13:35 UTC
    查看更新说明

    fse 1.1.0 is the first release since 2022 and the last feature release of the 1.x line. It brings fse to current Python, numpy, scipy and gensim, makes training scale with the number of cores, and stores vectors in less memory. From here on, 1.x receives bug and compatibility fixes; fse 2.0 is in development as a rewrite.

    Upgrading from 1.0

    • SIF and uSIF give different vectors. The principal components are now computed exactly over all sentence vectors instead of from a random sample, and uSIF weights the removed components like the reference implementation. Retrain SIF and uSIF models for vectors that are comparable with new ones; uSIF models saved by 1.0 keep their old weighting when loaded. On the STS Benchmark, uSIF scores rise by 0.2 to 1.4 points.
    • Arguments are validated strictly and raise a pydantic.ValidationError instead of being coerced or ignored.
    • Requires Python 3.11 to 3.13. New dependencies: pydantic and threadpoolctl.

    Highlights

    • Training scales with workers. Pass workers=os.cpu_count(). On 16 vCPUs of an AMD EPYC, 16 workers train up to 2.2 million sentences per second with Average and 1.7 million with SIF from a CorpusFile.
    • New inputs CorpusFile(path) and TextList(strings), read and split by every worker in compiled code.
    • Smaller vectors: sv_dtype and wv_dtype store sentence and word vectors as float16 or int8, with every read in float32. On the STS Benchmark, int8 changes the scores by at most 0.02 points for sentence vectors and 0.12 points for word vectors.
    • SentenceVectors.score(weights, bias) applies a linear model to all sentence vectors in blocks, and min_similarity filters the similarity searches.
    • Pre-built wheels for Linux (x86_64, aarch64), macOS (arm64, x86_64) and Windows (x86_64).
    • Many fixes, among them a 32-bit offset overflow that wrote sentence vectors into wrong rows from about 14 million sentences on, train(update=True) overwriting existing rows, and hangs when a worker failed.

    The full list is in CHANGELOG.md.

  3. v1.0.02022-04-10 · 2022-04-10 19:45 UTC
    查看更新说明

    🤘🏻 Finally added support for Gensim 4! 🚀

    • Added support for gensim>=4. This library is no longer compatible with gensim<4. For migration, see the README.
    • size argument is now vector_size
  4. v0.2.02021-12-03 · 2021-12-03 10:10 UTC
    查看更新说明
    • Added Vectors and FTVectors class and hub support by from_pretrained
    • Extended benchmark
    • Fixed zero division bug for uSIF
    • Moved tests out of the main folder
    • Moved sts out of the main folder
  5. v0.1.172021-12-01 · 2021-12-01 19:54 UTC
    查看更新说明

    Fixed dependency issue where you cannot install fse properly Updated readme Updated travis python versions (3.6, 3.9)

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Fast Sentence Embeddings turns text into vectors for search, clustering and classification. It runs ready-made static models on your CPU. No GPU needed. Use your Model2Vec model, a supported static Sentence Transformers model, or existing word vectors. Bring a trained linear classifier to label text in the same application.

    An engine turns pages of text into lists of numbers.

    1 100 million WikiText sentences (28.7 tokens on average) embedded and written to disk in 13.46 seconds. Float32 model table, int8 output, 16 workers, AMD EPYC 9575F (32 vCPU), RunPod, 2 November 2026.

    How we measured

    Speed first

    fse vs other libraries

    Each row compares the same model, input and float32 output in both libraries.

    AMD EPYC 9575F, 32 vCPU, RunPod, 2 November 2026. WikiText sentences, about 26 words each. Each bar is a paired comparison: 2 million sentences with one worker; 18 million with 16 workers for POTION-8M, 5 million for static-retrieval-mrl-en-v1.

    One worker
    LibraryModel (dimensions)fse, texts/sLibrary, texts/sSpeedup
    FastEmbedPOTION-8M (256)684,33229,28523.69×
    Model2VecPOTION-8M (256)701,92733,06521.36×
    sentence-transformersstatic-retrieval-mrl-en-v1 (1024)217,33818,55411.81×
    model2vec-rsPOTION-8M (256)625,842413,1731.50×

    AMD EPYC 9575F, 32 vCPU, RunPod, 2 November 2026. 2 million sentences per pair, about 26 words each.

    16 workers
    LibraryModel (dimensions)fse, texts/sLibrary, texts/sSpeedup
    Model2VecPOTION-8M (256)6,603,47652,772125.78×
    FastEmbedPOTION-8M (256)6,513,573290,27022.31×
    sentence-transformersstatic-retrieval-mrl-en-v1 (1024)2,064,693225,9279.14×
    model2vec-rsPOTION-8M (256)6,505,4892,775,3502.38×

    AMD EPYC 9575F, 32 vCPU, RunPod, 2 November 2026. 18 million sentences per POTION-8M pair; 5 million for the static-retrieval pair, about 26 words each.

    Model2Vec uses its default thread path on 16 allowed CPUs, using about four CPUs. Sentence Transformers uses batch size 32 with one worker. With 16 workers, it uses the recommended CPU process pool and batch size 1024.

    The Sentence Transformers model has 1024 dimensions, four times POTION-8M's 256. fse computes and writes four times as many numbers per sentence, so its absolute throughput is lower. The speedup compares both libraries on that same 1024-dimensional model.

    Performance on longer texts

    Encode longer texts with one or 16 workers:

    Model precision1 worker, texts/s16 workers, texts/s
    float32139,4221,265,123
    float16153,8861,359,540
    int8163,7791,379,949

    AMD EPYC 9965, 32 logical CPUs, RunPod, POTION-8M, 1 to 2 November 2026. 4.62 million WikiText-103 passages, average 119.1 words, float32 output.

    Get started

    pip install "fse>=2.0"
    

    Moving an existing application? Read the migration guide from fse 1.x before upgrading.

    Python 3.11 to 3.15. Wheels target Linux (x86_64 and aarch64, glibc or musl), macOS (Apple Silicon, arm64) and Windows (x86_64).

    Load a model and make a vector for each text:

    from fse import Encoder
    
    encoder = Encoder.from_pretrained("minishlab/potion-base-8M")
    vectors = encoder.encode(["the cat sleeps", "a dog runs"])
    

    A vector is a list of numbers that represents a text. Here you get a NumPy array with one row per text. For document vectors, pass your documents as strings. The model's text-length limits still apply. The first load downloads the model. Its license applies separately. For complete applications, explore the nine recipes below.

    Already using Model2Vec?

    Switch the import for the common loading and encoding calls:

    from fse import StaticModel  # replaces: from model2vec import StaticModel
    
    model = StaticModel.from_pretrained("minishlab/potion-base-8M")
    vectors = model.encode(["the cat sleeps", "a dog runs"])
    

    The supported API is similar, but conversion can change float32 rounding. save_pretrained and push_to_hub export fse folders. Reload them with fse.StaticModel.from_pretrained. They are not Model2Vec 0.10.0 export files.

    Using a static Sentence Transformers model?

    from fse import Encoder
    
    encoder = Encoder.from_pretrained("sentence-transformers/static-retrieval-mrl-en-v1")
    vectors = encoder.encode(["How do I reset my password?", "Open your account settings."])
    

    This supports compatible static models. It does not run contextual transformers such as MiniLM or BGE.

    Choose a model and memory budget

    Start with POTION-base-8M for a small English model. Use retrieval models for search and multilingual models for other languages. Compare quality and size below, then check the model on your own data.

    Model (Hub ID)Quality2Float32 file size
    minishlab/potion-base-32M55.26124.1 MiB
    minishlab/potion-retrieval-32M54.51124.1 MiB
    minishlab/potion-base-8M53.9529.2 MiB
    sentence-transformers/static-retrieval-mrl-en-v151.89119.7 MiB
    sentence-transformers/static-similarity-mrl-multilingual-v151.43415.1 MiB
    fse/fasttext-crawl-subwords-300-sif1051.424.50 GiB
    fse/paragram-300-sl999-sif1050.981.93 GiB
    fse/paranmt-300-raw50.6989.5 MiB
    minishlab/potion-multilingual-128M49.96502.1 MiB
    fse/glove-wiki-gigaword-300-sif1049.79463.6 MiB
    fse/word2vec-google-news-300-sif1049.263.41 GiB

    Start with float32. Try float16 or int8 when memory matters, then check quality again.

    from fse import Encoder
    
    compact = Encoder.from_pretrained("minishlab/potion-base-8M").quantize("int8")
    compact.save("compact.safetensors")
    

    See all 30 classic Hub models with float32, float16 and int8 download sizes. -raw in a classic Hub model name means no SIF weighting. It does not mean unnormalized output.

    You can also use your own GloVe, word2vec or FastText vectors with the optional fse[gensim] adapter. The SIF and uSIF recipe shows how to prepare classic word vectors for sentence search.

    Recipes: from a model to a working application

    These recipes show the full workflow, including preparation, execution and checks. Start with the job you want to do:

    Use caseRecipe
    Start here: fse 101Turn sentences into vectors, find similar text, and save your results.
    Sentence searchMake classic word vectors more useful for sentence search.
    Text classificationTrain a text classifier, compare its accuracy, and save it for your app.
    Files with resumeTurn a text file into vectors and continue after an interruption.
    Queues and QdrantTurn queued news into searchable vectors and replay safely.
    Folder listenerPick up completed files and add each one to your search store.
    IntegrationsUse one local encoder with your search, topic, and table libraries.
    Local compute and HTTPSee when a local HTTP request costs more than encoding the text.
    Folded classifierFold a trained classifier into word vectors, then save a smaller model for your app.
    External indexesPut FAISS, USearch, or hnswlib on your vectors, then let fse give the hits exact scores.
    Rerank candidatesLet fse find 100 candidates, then let a local cross-encoder of your choice put the best ones on top.
    Filter large text collectionsStore scores with document IDs and change filters without running the encoder again.
    FastAPI searchAnswer many searches at once from one store, then from three stores on a shared drive.

    The recipe index has setup instructions and recorded results. The queue recipe uses a fake queue and local Qdrant. The folder recipe polls for completed files. These are worked examples you can adapt, not hosted services.

    Linear heads: classify text and save scores

    Route support requests, filter documents or classify a dataset. Train a supported linear model in scikit-learn, then let fse run it directly on text. The model must be trained on this encoder's vectors. normalize=False works for every supported model, including our classic Hub models under fse/. Our classic Hub models load normalized by default. For folding, load with normalize=False before training the linear head. Install scikit-learn first: pip install scikit-learn.

    from sklearn.linear_model import LogisticRegression
    import fse
    from fse import Classifier, Encoder
    
    encoder = Encoder.from_pretrained("minishlab/potion-base-8M", normalize=False)
    texts = ["I cannot log in", "My password is lost", "Send me a quote", "What does it cost?"]
    head = LogisticRegression().fit(encoder.encode(texts), ["support", "support", "sales", "sales"])
    classifier = Classifier.from_sklearn(encoder, head)
    labels = classifier.predict(["I need a new password"])
    classifier.save("router.safetensors")
    

    Train on real labeled data for your application. Load the saved model with Classifier.load("router.safetensors") and predict without scikit-learn.

    The classification recipe compares this with Model2Vec's own classifier, TF-IDF and fastText.

    Several heads in one pass

    fse.Heads groups named heads from the same encoder. Each head keeps its own labels and file. The group tokenizes each text once. This tiny training set only demonstrates the API.

    import numpy as np
    from sklearn.multiclass import OneVsRestClassifier
    
    indicator = np.array([[1, 0], [1, 0], [0, 1], [0, 1]])
    model = OneVsRestClassifier(LogisticRegression()).fit(encoder.encode(texts), indicator)
    tags = Classifier.from_sklearn(encoder, model, labels=["support", "sales"])
    heads = fse.Heads(route=classifier, tags=tags)
    outputs = heads.predict(texts, output="proba")  # a mapping, one array per head
    

    Save scores with document IDs

    Store decisions (logits, the values before the probability rule) to derive probabilities and labels later. A score store needs no sentence vectors. Use a fresh directory for this example.

    doc_ids = np.array([101, 102, 103, 104], dtype=np.int64)
    with fse.ScoreSink("request-scores", heads=heads, doc_ids=True) as sink:
        for batch in heads.predict_batches([(texts, doc_ids)], doc_ids=True, score_sink=sink):
            pass
    scores = fse.ScoreStore.open("request-scores", heads=heads)
    scores.verify()
    np.testing.assert_array_equal(scores.get_doc_ids(), doc_ids)
    

    Change filters after inference

    Multilabel heads test each label independently, strictly above its threshold. Equality is false. predict_batches derives masks from stored scores. Your code uses a mask to select IDs.

    for batch in scores.predict_batches(thresholds={"tags": {"support": 0.7}}):
        support = batch.outputs["tags"][:, 0]
        selected_ids = batch.doc_ids[support]
    scores.close()
    

    Save all scores for unrestricted later thresholds. If you discard rows, softer thresholds cannot recover them from that subset. The filtering tutorial checks this with seven labels on a small Civil Comments sample. Its counts demonstrate the workflow, not moderation quality.

    Write vectors and scores in one run

    Use two sinks and two folders. return_vectors=True computes full sentence vectors, so raw-mean heads take the unfolded path. This gives up the narrower pooling of folding. Each folder is durable separately, without an atomic commit across folders. batch.durable_rows confirms the smaller durable prefix of the two sinks. Read the final count from both sinks after the iterator finishes and flushes its last partial shard.

    with fse.ShardSink("request-vectors", encoder, doc_ids=True) as vectors, fse.ScoreSink(
        "request-scores-with-vectors", heads=heads, doc_ids=True
    ) as sink:
        for batch in heads.predict_batches(
            [(texts, doc_ids)], doc_ids=True, return_vectors=True,
            vector_sink=vectors, score_sink=sink,
        ):
            confirmed_rows = batch.durable_rows
        confirmed_rows = min(vectors.durable_rows, sink.durable_rows)  # after the final flush
    

    Import a linear Model2Vec classifier

    A finished Model2Vec classifier with one linear layer can also run through fse. Hidden layers are rejected. Pass a trained classification object or its exported pipeline:

    # trained_model2vec is your finished StaticModelForClassification with n_layers=0.
    imported = Classifier.from_model2vec(trained_model2vec)
    imported.save("imported-classifier.safetensors")
    

    For an exported local folder or a Hub repository, use Classifier.from_pretrained(path_or_repo, revision=commit). See the head formats for supported links and import limits. These additions require fse from master until 2.1 is released.

    Why folding works

    A sentence vector averages the vectors of its words or word pieces. A linear head multiplies that average by weights W and adds a bias b. Multiplying and averaging can swap order:

    W · mean(v) + b = mean(W · v) + b

    fse computes W · v once for each word and stores the scores. A four-label classifier can then keep 4 scores per word instead of 256 values. Prediction looks up those scores, averages them and adds the bias. There is no full sentence vector or head matrix multiplication per text.

    The folding recipe found identical AG News labels, with probability differences up to about 0.000003 from rounding. The complete POTION-8M file uses 29.2 MiB, compared with 0.86 MiB for the standalone file.

    POTION-8M, AG News, Apple M1 Pro, 1 November 2026.

    This works for linear classification and regression heads trained on plain averages, including logistic regression, LinearSVC and Ridge, without requesting sentence vectors. fse runs linear heads trained on normalized vectors explicitly, because normalization prevents this fold. Neural heads with hidden layers cannot use this fold, and fse does not run them.

    Use fse with your framework

    Use the same local encoder in LangChain, LlamaIndex, or Haystack. Each framework has a separate package, so fse core needs no framework dependency.

    FrameworkInstallEmbedding interface
    LangChainpip install langchain-fselangchain_fse.FseEmbeddings
    LlamaIndexpip install llama-index-embeddings-fsellama_index.embeddings.fse.FseEmbedding
    Haystackpip install fse-haystackFseTextEmbedder and FseDocumentEmbedder from haystack_integrations.components.embedders.fse

    See recipe 06 for complete local search examples with POTION and real news articles.

    Did the model cut my text?

    Static models cut long texts without a word: their tokenizer keeps at most a fixed number of tokens (512 for Model2Vec models). Ask for a report to see how many texts were cut and by how much:

    import fse
    
    model = fse.Encoder.from_pretrained("minishlab/potion-base-8M")
    vectors, report = model.encode(texts, lengths="per_row")
    print(report.n_truncated, "of", report.n_rows, "texts were cut")
    cut = report.truncated_rows()  # their positions
    

    lengths="summary" keeps only the sums (use it for big streams), count_truncated=True adds how many tokens and characters were cut, and on_truncation="raise" raises fse.TruncationError for the first text the model would cut. The same keywords work on encode_batches (each batch comes with its own report), on fse.Heads and on a single Classifier or Regressor (labels, report = clf.predict(texts, lengths="summary")). report.empty marks texts without any token: they become the zero vector. A zero vector can have other causes, so it does not always mean bad text. Without these keywords nothing is recorded and nothing changes. A text that reaches the cut costs at most one more word to find out whether more follows; counting the cut tokens tokenizes the rest of the text.

    When to use fse

    Choose fse when throughput matters more than squeezing out the highest possible model quality. Static models give each word or word piece a fixed vector. They do less work than contextual transformers. That makes them useful for large collections, local applications and repeated classification. A good contextual transformer may handle meaning, ambiguity and difficult search tasks better. Check the quality on your own data before choosing the faster model.

    This trade-off concerns the model you choose. Switching a Model2Vec static model to fse does not mean choosing a weaker model. fse runs finished models; it does not train or distill them.

    ToolA good fit when you want to...
    fseRun static embeddings, fitted linear classifiers and file or stream jobs on a CPU.
    Model2VecCreate, distill or fine-tune static models, as well as run them.
    Sentence TransformersRun or train contextual transformers, or use its static models and broader model ecosystem.
    gensim / Doc2VecTrain document vectors or word-vector models in gensim.

    Why is fse so fast?

    We build and tune the whole path, from reading text to writing results. That includes splitting text into tokens, finding their vectors, combining them and saving the output. The goal is to keep useful work together and avoid overhead between steps.

    Design choiceWhat it does
    Prepare onceReuse a finished model's fixed word vectors for every text.
    Keep work togetherThe CPU fast path groups tokenization and vector calculation in compiled code.
    Share the workCPU workers process batches in parallel.
    Control the output pathFiles and streams can write to fse's vector store without collecting all results in memory.

    Memory use

    Large output arrays can take more space than the model itself. For a list of texts with float32 output, fse allocates the result array once. CPU workers write directly into their rows. There is no final join of separate result arrays.

    The batch paths in Model2Vec 0.10.0 and Sentence Transformers 6.2.0 collect results, then join them with np.concatenate or torch.stack. During that join, the parts and the final array coexist, using roughly twice the space for output data alone. This is not a measurement of total memory use.

    Size calculation: 18 million texts × 256 values × 4 bytes = 18.4 GB per float32 matrix. For results larger than RAM, write to a ShardSink or the vector store. fse limits the output work in flight. Returning one array from an input of unknown length still collects and joins parts, so use a disk sink for large streams.

    More

    fse 2.0 uses the MIT License. The 1.x releases remain GPL-3.0. Model and dataset licenses apply separately. Built by Dr. Oliver Borchers.

    Footnotes

    1. Median of three runs. On the same machine, 32 workers took 16.75 seconds, so we use 16 workers here. ↩

    2. MTEB(eng, v2), measured on 1 November 2026 with MTEB 2.26.25. Scores are the unweighted mean over 40 of 41 English tasks, multiplied by 100. MIND was not computed. Classic-model scores use the historical benchmark recipe. SIF-10 scores use a WikiText-103 fit, whereas the linked Hub SIF-10 models use wordfreq weights and a FineWeb fit. See each Hub card for that version's own results. Sizes refer to float32 fse files. Quality results and file sizes. ↩