[
https://issues.apache.org/jira/browse/OPENNLP-1877?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18094458#comment-18094458
]
Kristian Rickert commented on OPENNLP-1877:
-------------------------------------------
[https://github.com/apache/opennlp/pull/1152] has been created
> Pure JVM Embeddings - opennlp-extensions/opennlp-embeddings module
> ------------------------------------------------------------------
>
> Key: OPENNLP-1877
> URL: https://issues.apache.org/jira/browse/OPENNLP-1877
> Project: OpenNLP
> Issue Type: New Feature
> Affects Versions: 3.0.0-M4
> Reporter: Kristian Rickert
> Assignee: Kristian Rickert
> Priority: Major
> Fix For: 3.0.0-M5
>
> Time Spent: 10m
> Remaining Estimate: 0h
>
> h2. Summary
> OpenNLP's static word-vector support ends at the GloVe loader in
> {{opennlp.tools.util.wordvector}}. The static-embedding field moved in
> 2024/2025: distillation tooling (Model2Vec and compatible) compresses a full
> sentence transformer into a flat per-token vector table, the same artifact
> shape as word2vec/GloVe, carrying the teacher model's semantics. Inference
> over such a table is tokenize, gather, weight, mean-pool, normalize: no
> forward pass, no GPU, no native runtime. That is a pure-JVM sweet spot, and
> this ticket adds the engine for it as a new
> {{opennlp-extensions/opennlp-embeddings}} module.
> The module ships code only. Users point it at a table they downloaded (the
> {{vocab.txt}} plus {{model.safetensors}} file pair such distillations
> publish); nothing is fetched at build or run time and no model weights enter
> the source tree or release artifacts. The pooling semantics were verified
> against the reference implementations (the Model2Vec Python package and its
> official Rust port), not assumed: {{[CLS]}}/{{[SEP]}} are never pooled,
> unknown tokens are dropped from both the sum and the denominator, an optional
> per-row {{weights}} tensor multiplies each token's vector before pooling, the
> denominator is the pooled-token count (not the sum of weights), and
> normalization uses an epsilon floor so token-less input yields a zero vector
> rather than NaN.
> Measured with JMH at the scale of a current published table (29,528 rows by
> 256 dimensions): about 766,000 short-sentence embeds per second on one core,
> and the full-vocabulary top-10 similarity scan at 649 per second on one core
> scaling to about 9,200 per second at 32 threads. Instances are immutable and
> annotated {{@ThreadSafe}}, with a concurrency test comparing every concurrent
> result against the single-threaded reference.
> h2. Scope
> * {{SafetensorsFile}}: a reader for the safetensors tensor format (8-byte
> little-endian header length, JSON header, raw tensor bytes) with a
> purpose-built cursor parser for the header, no third-party JSON dependency.
> Only the F32 decode path is implemented. Unlike pickle-based checkpoint
> formats, safetensors carries no executable content, so loading is safe by
> construction. The embedding matrix is found as the single 2-D F32 tensor,
> failing loud and listing candidates when that is ambiguous, rather than
> guessing a key-name convention.
> * {{WordPieceVocabulary}}: a BERT-style {{vocab.txt}} reader; the line number
> is the token's row id into the embedding matrix.
> * {{StaticEmbeddingModel}}: loads the file pair, embeds text through the
> existing {{BertTokenizer}}/{{WordpieceTokenizer}} (reused as is, no new
> tokenizer code), and applies the verified pooling formula. Per-row L2 norms
> and the special-token mask are precomputed at load.
> * Convenience surface: {{similarity(text1, text2)}}, {{mostSimilar(text,
> topK)}} (brute-force scan with a bounded top-K selection), and {{analogy(a,
> b, c, topK)}} (vector arithmetic, with the input terms excluded by folding
> them through the model's own tokenizer, so exclusion is case- and
> accent-consistent with embedding).
> * A JMH benchmark behind the same opt-in {{jmh}} Maven profile pattern
> {{opennlp-runtime}} already uses; the default build is unaffected.
> h2. Acceptance criteria
> * Loads the file pair of a current published distilled table and matches the
> reference pooling semantics; tests pin the formula details (denominator,
> unknown-token handling, weight application, normalization epsilon, zero-row
> NaN guard).
> * No bundled model data, no new dependencies, nothing fetched at build or run
> time.
> * Immutable and thread-safe; a concurrency test compares concurrent results
> against the single-threaded reference.
> * {{mvn verify}} green including checkstyle and forbiddenapis; JMH numbers
> recorded in the PR.
> h2. Out of scope
> * Bundling any model weights as a zero-config default. That requires
> per-release license diligence on the weights (the distillation tool being MIT
> does not settle the weight license) and is a separable later decision.
> * An approximate-nearest-neighbor index for {{mostSimilar}}. Brute force is
> the documented v1 posture at this vocabulary scale; an index is a follow-up.
> * The gRPC {{EmbeddingProvider}} backend in opennlp-sandbox that serves this
> engine. That is a companion change in the sandbox repository after this lands.
> * Interpreting the Hugging Face {{tokenizer.json}} pipeline format. The v1
> contract is {{vocab.txt}} plus the existing WordPiece tokenizer, which the
> targeted table family publishes.
> * Training or distillation. Producing tables stays in the upstream tooling,
> the same posture opennlp-dl takes for ONNX models.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)