Skip to main content

bge_m3_embedding_server/embedder/
types.rs

1// Copyright (c) 2026 J. Patrick Fulton
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Public DTOs and the internal `EmbedRequest` enum exchanged between the
16//! pool and the worker threads.
17
18use anyhow::Result;
19use tokio::sync::oneshot;
20
21/// Sparse embedding output from the BGE-M3 sparse-linear projection layer.
22///
23/// Represents a document as a sparse vector over the tokenizer vocabulary.
24/// Token IDs with zero ReLU-gated score are omitted.
25#[derive(Debug, Clone)]
26pub struct SparseEmbedding {
27    /// Sorted vocabulary token IDs with non-zero ReLU-gated weight.
28    pub indices: Vec<usize>,
29    /// Corresponding ReLU-gated projection scores, in the same order as `indices`.
30    pub values: Vec<f32>,
31}
32
33/// Paired dense + sparse embeddings produced from a single forward pass.
34#[derive(Debug, Clone)]
35pub struct DualEmbedding {
36    pub dense: Vec<f32>,
37    pub sparse: SparseEmbedding,
38}
39
40/// OS headroom reserved for kernel, stack, ORT arena, and other non-model
41/// allocations. Subtracted from available memory before computing
42/// per-worker workspace.
43pub(crate) const OS_HEADROOM_BYTES: usize = 256 * 1024 * 1024; // 256 MiB
44
45/// Per-request diagnostic statistics captured inside the worker and forwarded
46/// to the handler layer for inclusion in the completion log event.
47#[derive(Debug, Clone, Copy, Default)]
48pub struct EmbedStats {
49    /// Number of bin-packed chunks the batch was split into.
50    pub chunks: usize,
51    /// Maximum tokenized sequence length across all chunks.
52    pub max_chunk_seq: usize,
53    /// Total token-positions processed (sum of `seq_len` for all inputs).
54    pub total_token_positions: usize,
55    /// Time spent tokenizing all inputs (milliseconds).
56    pub tokenize_ms: u64,
57    /// Total time spent in ORT `session.run()` across all chunks (milliseconds).
58    pub inference_ms: u64,
59    /// Minimum token sequence length across all inputs in the batch.
60    pub seq_len_min: usize,
61    /// Maximum token sequence length across all inputs in the batch.
62    pub seq_len_max: usize,
63    /// Mean token sequence length across all inputs (integer, truncated).
64    pub seq_len_mean: usize,
65    /// 95th-percentile token sequence length across all inputs in the batch.
66    ///
67    /// Index is `(n * 95) / 100` on a sorted copy of the per-input lengths.
68    pub seq_len_p95: usize,
69}
70
71pub(crate) enum EmbedRequest {
72    /// Dense (float32) embedding inference on a batch of texts.
73    Dense {
74        texts: Vec<String>,
75        reply: oneshot::Sender<Result<(Vec<Vec<f32>>, EmbedStats)>>,
76    },
77    /// Sparse (SPLADE-style) embedding inference on a batch of texts.
78    Sparse {
79        texts: Vec<String>,
80        reply: oneshot::Sender<Result<(Vec<SparseEmbedding>, EmbedStats)>>,
81    },
82    /// Computes dense and sparse embeddings from a single forward pass per chunk.
83    Both {
84        texts: Vec<String>,
85        reply: oneshot::Sender<Result<(Vec<DualEmbedding>, EmbedStats)>>,
86    },
87    /// Internal: used during startup probe to run a single batch and measure
88    /// peak RSS delta. Workers only process this before `ready` is set.
89    Probe {
90        texts: Vec<String>,
91        reply: oneshot::Sender<Result<ProbeResult>>,
92    },
93    /// Adaptive background warmup: asks a worker to compile (or confirm as
94    /// cached) the TRT engine for `(batch, seq)`.  The worker replies on
95    /// `ack` with the compile duration in milliseconds, or an error if the
96    /// shape failed.  Only meaningful on TRT EP; on CPU/CUDA workers the
97    /// worker returns `Ok(0)` immediately.
98    AdaptiveWarmup {
99        batch: usize,
100        seq: usize,
101        ack: oneshot::Sender<anyhow::Result<u64>>,
102    },
103}
104
105/// Sender half of the JIT-suspect channel.
106///
107/// Workers hold an optional clone of this sender and call `try_send`
108/// (non-blocking, drops if full) after any inference whose `inference_ms`
109/// equals or exceeds the TRT cache-hit threshold.
110pub(crate) type JitSuspectSender = tokio::sync::mpsc::Sender<(usize, usize)>;
111
112/// Result of a single probe `session.run()` call.
113#[derive(Debug)]
114pub(crate) struct ProbeResult {
115    /// Process RSS (bytes) measured immediately before `session.run()`.
116    pub rss_before: usize,
117    /// Process RSS (bytes) measured immediately after `session.run()`.
118    pub rss_after: usize,
119}