bge_m3_embedding_server/embedder/worker/
dispatch.rs1use std::collections::HashSet;
18use std::path::Path;
19use std::sync::atomic::Ordering;
20
21use super::config::WorkerConfig;
22use super::guard::{
23 EmbedRouteContext, InferenceOutcome, adaptive_warmup_non_trt_compile_ms, finalize_embed_route,
24 log_client_abandoned_before_dispatch,
25};
26use super::probe::run_probe_batch;
27use super::trt_retry::embed_with_trt_retry;
28use crate::config::EpSelection;
29use crate::embedder::dense::embed_dense;
30use crate::embedder::dual::embed_both;
31use crate::embedder::jit_guard::TrtJitGuard;
32use crate::embedder::sparse::embed_sparse;
33use crate::embedder::trt_warmup::trt_prewarm;
34use crate::embedder::types::EmbedRequest;
35
36pub(super) struct DispatchOutcome {
38 pub outcome: InferenceOutcome,
39 pub skip: bool,
40}
41
42pub(super) fn reply_request_load_error(request: EmbedRequest, err: anyhow::Error) {
44 match request {
45 EmbedRequest::Dense { reply, .. } => {
46 let _ = reply.send(Err(err));
47 }
48 EmbedRequest::Sparse { reply, .. } => {
49 let _ = reply.send(Err(err));
50 }
51 EmbedRequest::Both { reply, .. } => {
52 let _ = reply.send(Err(err));
53 }
54 EmbedRequest::Probe { reply, .. } => {
55 let _ = reply.send(Err(err));
56 }
57 EmbedRequest::AdaptiveWarmup { ack, .. } => {
58 let _ = ack.send(Err(err));
59 }
60 }
61}
62
63#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
65pub(super) fn dispatch_request(
66 request: EmbedRequest,
67 session: &mut ort::session::Session,
68 tokenizer: &mut tokenizers::Tokenizer,
69 config: &WorkerConfig,
70 id: usize,
71 cache_dir: &Path,
72 detected_sm: Option<&str>,
73 warmed_local: &mut HashSet<(usize, usize)>,
74 consecutive_failures: u64,
75 shape_guard: Option<&TrtJitGuard>,
76) -> DispatchOutcome {
77 let mut inner_skip = false;
78 let mut inner_outcome = InferenceOutcome::Ok;
79
80 let route_ctx = EmbedRouteContext {
81 worker_id: id,
82 route: "",
83 consecutive_failures,
84 circuit_breaker_threshold: config.circuit_breaker_threshold,
85 jit_suspect_tx: config.jit_suspect_tx.as_ref(),
86 engine_propagation_tx: config.engine_propagation_tx.as_ref(),
87 batch_len: 0,
88 };
89
90 match request {
91 EmbedRequest::Dense { texts, reply } => {
92 if reply.is_closed() {
101 log_client_abandoned_before_dispatch(id, "dense", texts.len());
102 inner_skip = true;
103 } else {
104 let t_inference = std::time::Instant::now();
105 let cm_guard = config.cost_model.load();
106 let result = embed_with_trt_retry(
107 |cm| {
108 embed_dense(
109 session,
110 tokenizer,
111 &texts,
112 cm,
113 config.model_variant,
114 shape_guard,
115 )
116 },
117 &cm_guard,
118 id,
119 "dense",
120 )
121 .map_err(|e| e.context("Dense embed error"));
122 let inference_ms = t_inference.elapsed().as_millis();
123 let ctx = EmbedRouteContext {
124 route: "dense",
125 batch_len: texts.len(),
126 ..route_ctx
127 };
128 inner_outcome =
129 finalize_embed_route(&ctx, result, reply, inference_ms, warmed_local);
130 }
131 }
132 EmbedRequest::Sparse { texts, reply } => {
133 if reply.is_closed() {
134 log_client_abandoned_before_dispatch(id, "sparse", texts.len());
135 inner_skip = true;
136 } else {
137 let t_inference = std::time::Instant::now();
138 let cm_guard = config.cost_model.load();
139 let result = embed_with_trt_retry(
140 |cm| {
141 embed_sparse(
142 session,
143 tokenizer,
144 &texts,
145 cm,
146 config.model_variant,
147 shape_guard,
148 )
149 },
150 &cm_guard,
151 id,
152 "sparse",
153 )
154 .map_err(|e| e.context("Sparse embed error"));
155 let inference_ms = t_inference.elapsed().as_millis();
156 let ctx = EmbedRouteContext {
157 route: "sparse",
158 batch_len: texts.len(),
159 ..route_ctx
160 };
161 inner_outcome =
162 finalize_embed_route(&ctx, result, reply, inference_ms, warmed_local);
163 }
164 }
165 EmbedRequest::Both { texts, reply } => {
166 if reply.is_closed() {
167 log_client_abandoned_before_dispatch(id, "both", texts.len());
168 inner_skip = true;
169 } else {
170 let t_inference = std::time::Instant::now();
171 let cm_guard = config.cost_model.load();
172 let result = embed_with_trt_retry(
173 |cm| {
174 embed_both(
175 session,
176 tokenizer,
177 &texts,
178 cm,
179 config.model_variant,
180 shape_guard,
181 )
182 },
183 &cm_guard,
184 id,
185 "both",
186 )
187 .map_err(|e| e.context("Dual embed error"));
188 let inference_ms = t_inference.elapsed().as_millis();
189 let ctx = EmbedRouteContext {
190 route: "both",
191 batch_len: texts.len(),
192 ..route_ctx
193 };
194 inner_outcome =
195 finalize_embed_route(&ctx, result, reply, inference_ms, warmed_local);
196 }
197 }
198 EmbedRequest::Probe { texts, reply } => {
199 let result = run_probe_batch(session, tokenizer, &texts);
203 let _ = reply.send(result);
204 }
205 EmbedRequest::AdaptiveWarmup { batch, seq, ack } => {
206 let result: anyhow::Result<u64> = if config.ep == EpSelection::TensorRt {
210 let shape = vec![(batch, seq)];
211 let stats = trt_prewarm(session, &shape, id, cache_dir, detected_sm);
212 if stats.warmed > 0 || stats.fully_cached {
213 config
217 .warmed_seq_ceiling
218 .fetch_max(stats.max_warmed_seq.max(seq), Ordering::AcqRel);
219 Ok(stats.total_compile_ms)
220 } else {
221 Err(anyhow::anyhow!(
222 "adaptive warmup: no shapes warmed for \
223 ({batch}, {seq}) on worker {id}"
224 ))
225 }
226 } else {
227 Ok(adaptive_warmup_non_trt_compile_ms())
228 };
229 let _ = ack.send(result);
230 }
231 } DispatchOutcome {
234 outcome: inner_outcome,
235 skip: inner_skip,
236 }
237}