Skip to content

Commit 14af074

Browse files
authored
feat: Media decoder and fetcher options in the MDC (#4094)
Signed-off-by: Alexandre Milesi <[email protected]>
1 parent e17b046 commit 14af074

File tree

6 files changed

+105
-3
lines changed

6 files changed

+105
-3
lines changed

lib/bindings/python/rust/lib.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use dynamo_llm::{self as llm_rs};
3939
use dynamo_llm::{entrypoint::RouterConfig, kv_router::KvRouterConfig};
4040

4141
use crate::llm::local_model::ModelRuntimeConfig;
42+
use crate::llm::preprocessor::{MediaDecoder, MediaFetcher};
4243

4344
#[pyclass(eq, eq_int)]
4445
#[derive(Clone, Debug, PartialEq)]
@@ -161,6 +162,8 @@ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
161162
m.add_class::<llm::model_card::ModelDeploymentCard>()?;
162163
m.add_class::<llm::local_model::ModelRuntimeConfig>()?;
163164
m.add_class::<llm::preprocessor::OAIChatPreprocessor>()?;
165+
m.add_class::<llm::preprocessor::MediaDecoder>()?;
166+
m.add_class::<llm::preprocessor::MediaFetcher>()?;
164167
m.add_class::<llm::backend::Backend>()?;
165168
m.add_class::<llm::kv::OverlapScores>()?;
166169
m.add_class::<llm::kv::KvIndexer>()?;
@@ -217,7 +220,7 @@ fn log_message(level: &str, message: &str, module: &str, file: &str, line: u32)
217220
/// Create an engine and attach it to an endpoint to make it visible to the frontend.
218221
/// This is the main way you create a Dynamo worker / backend.
219222
#[pyfunction]
220-
#[pyo3(signature = (model_input, model_type, endpoint, model_path, model_name=None, context_length=None, kv_cache_block_size=None, router_mode=None, migration_limit=0, runtime_config=None, user_data=None, custom_template_path=None))]
223+
#[pyo3(signature = (model_input, model_type, endpoint, model_path, model_name=None, context_length=None, kv_cache_block_size=None, router_mode=None, migration_limit=0, runtime_config=None, user_data=None, custom_template_path=None, media_decoder=None, media_fetcher=None))]
221224
#[allow(clippy::too_many_arguments)]
222225
fn register_llm<'p>(
223226
py: Python<'p>,
@@ -233,6 +236,8 @@ fn register_llm<'p>(
233236
runtime_config: Option<ModelRuntimeConfig>,
234237
user_data: Option<&Bound<'p, PyDict>>,
235238
custom_template_path: Option<&str>,
239+
media_decoder: Option<MediaDecoder>,
240+
media_fetcher: Option<MediaFetcher>,
236241
) -> PyResult<Bound<'p, PyAny>> {
237242
// Validate Prefill model type requirements
238243
if model_type.inner == llm_rs::model_type::ModelType::Prefill {
@@ -305,7 +310,9 @@ fn register_llm<'p>(
305310
.migration_limit(Some(migration_limit))
306311
.runtime_config(runtime_config.unwrap_or_default().inner)
307312
.user_data(user_data_json)
308-
.custom_template_path(custom_template_path_owned);
313+
.custom_template_path(custom_template_path_owned)
314+
.media_decoder(media_decoder.map(|m| m.inner))
315+
.media_fetcher(media_fetcher.map(|m| m.inner));
309316
// Load the ModelDeploymentCard
310317
let mut local_model = builder.build().await.map_err(to_pyerr)?;
311318
// Advertise ourself on etcd so ingress can find us

lib/bindings/python/rust/llm/preprocessor.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
use super::*;
55
use crate::llm::model_card::ModelDeploymentCard;
6+
use std::time::Duration;
67

78
use llm_rs::{
89
preprocessor::OpenAIPreprocessor,
10+
preprocessor::media::{MediaDecoder as RsMediaDecoder, MediaFetcher as RsMediaFetcher},
911
protocols::common::llm_backend::{BackendOutput, PreprocessedRequest},
1012
types::{
1113
Annotated,
@@ -74,3 +76,62 @@ impl OAIChatPreprocessor {
7476
})
7577
}
7678
}
79+
80+
#[pyclass]
81+
#[derive(Clone)]
82+
pub struct MediaDecoder {
83+
pub(crate) inner: RsMediaDecoder,
84+
}
85+
86+
#[pymethods]
87+
impl MediaDecoder {
88+
#[new]
89+
fn new() -> Self {
90+
Self {
91+
inner: RsMediaDecoder::default(),
92+
}
93+
}
94+
95+
fn image_decoder(&mut self, image_decoder: &Bound<'_, PyDict>) -> PyResult<()> {
96+
let image_decoder = pythonize::depythonize(image_decoder).map_err(|err| {
97+
PyErr::new::<PyException, _>(format!("Failed to parse image_decoder: {}", err))
98+
})?;
99+
self.inner.image_decoder = image_decoder;
100+
Ok(())
101+
}
102+
}
103+
104+
#[pyclass]
105+
#[derive(Clone)]
106+
pub struct MediaFetcher {
107+
pub(crate) inner: RsMediaFetcher,
108+
}
109+
110+
#[pymethods]
111+
impl MediaFetcher {
112+
#[new]
113+
fn new() -> Self {
114+
Self {
115+
inner: RsMediaFetcher::default(),
116+
}
117+
}
118+
fn user_agent(&mut self, user_agent: String) {
119+
self.inner.user_agent = user_agent;
120+
}
121+
122+
fn allow_direct_ip(&mut self, allow: bool) {
123+
self.inner.allow_direct_ip = allow;
124+
}
125+
126+
fn allow_direct_port(&mut self, allow: bool) {
127+
self.inner.allow_direct_port = allow;
128+
}
129+
130+
fn allowed_media_domains(&mut self, domains: Vec<String>) {
131+
self.inner.allowed_media_domains = Some(domains.into_iter().collect());
132+
}
133+
134+
fn timeout_ms(&mut self, timeout_ms: u64) {
135+
self.inner.timeout = Some(Duration::from_millis(timeout_ms));
136+
}
137+
}

lib/bindings/python/src/dynamo/llm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from dynamo._core import KvRecorder as KvRecorder
1919
from dynamo._core import KvRouterConfig as KvRouterConfig
2020
from dynamo._core import KvStats as KvStats
21+
from dynamo._core import MediaDecoder as MediaDecoder
22+
from dynamo._core import MediaFetcher as MediaFetcher
2123
from dynamo._core import ModelInput as ModelInput
2224
from dynamo._core import ModelRuntimeConfig as ModelRuntimeConfig
2325
from dynamo._core import ModelType as ModelType

lib/llm/src/local_model.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::entrypoint::RouterConfig;
1414
use crate::mocker::protocols::MockEngineArgs;
1515
use crate::model_card::{self, ModelDeploymentCard};
1616
use crate::model_type::{ModelInput, ModelType};
17+
use crate::preprocessor::media::{MediaDecoder, MediaFetcher};
1718
use crate::request_template::RequestTemplate;
1819

1920
pub mod runtime_config;
@@ -52,6 +53,8 @@ pub struct LocalModelBuilder {
5253
namespace: Option<String>,
5354
custom_backend_metrics_endpoint: Option<String>,
5455
custom_backend_metrics_polling_interval: Option<f64>,
56+
media_decoder: Option<MediaDecoder>,
57+
media_fetcher: Option<MediaFetcher>,
5558
}
5659

5760
impl Default for LocalModelBuilder {
@@ -77,6 +80,8 @@ impl Default for LocalModelBuilder {
7780
namespace: Default::default(),
7881
custom_backend_metrics_endpoint: Default::default(),
7982
custom_backend_metrics_polling_interval: Default::default(),
83+
media_decoder: Default::default(),
84+
media_fetcher: Default::default(),
8085
}
8186
}
8287
}
@@ -184,6 +189,16 @@ impl LocalModelBuilder {
184189
self
185190
}
186191

192+
pub fn media_decoder(&mut self, media_decoder: Option<MediaDecoder>) -> &mut Self {
193+
self.media_decoder = media_decoder;
194+
self
195+
}
196+
197+
pub fn media_fetcher(&mut self, media_fetcher: Option<MediaFetcher>) -> &mut Self {
198+
self.media_fetcher = media_fetcher;
199+
self
200+
}
201+
187202
/// Make an LLM ready for use:
188203
/// - Download it from Hugging Face (and NGC in future) if necessary
189204
/// - Resolve the path
@@ -219,6 +234,8 @@ impl LocalModelBuilder {
219234
self.runtime_config.max_num_batched_tokens =
220235
mocker_engine_args.max_num_batched_tokens.map(|v| v as u64);
221236
self.runtime_config.data_parallel_size = mocker_engine_args.dp_size;
237+
self.media_decoder = Some(MediaDecoder::default());
238+
self.media_fetcher = Some(MediaFetcher::default());
222239
}
223240

224241
// frontend and echo engine don't need a path.
@@ -230,6 +247,8 @@ impl LocalModelBuilder {
230247
card.migration_limit = self.migration_limit;
231248
card.user_data = self.user_data.take();
232249
card.runtime_config = self.runtime_config.clone();
250+
card.media_decoder = self.media_decoder.clone();
251+
card.media_fetcher = self.media_fetcher.clone();
233252

234253
return Ok(LocalModel {
235254
card,
@@ -280,6 +299,8 @@ impl LocalModelBuilder {
280299
card.migration_limit = self.migration_limit;
281300
card.user_data = self.user_data.take();
282301
card.runtime_config = self.runtime_config.clone();
302+
card.media_decoder = self.media_decoder.clone();
303+
card.media_fetcher = self.media_fetcher.clone();
283304

284305
Ok(LocalModel {
285306
card,

lib/llm/src/model_card.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use dynamo_runtime::{slug::Slug, storage::key_value_store::Versioned};
2525
use serde::{Deserialize, Serialize};
2626
use tokenizers::Tokenizer as HfTokenizer;
2727

28+
use crate::preprocessor::media::{MediaDecoder, MediaFetcher};
2829
use crate::protocols::TokenIdType;
2930

3031
/// Identify model deployment cards in the key-value store
@@ -217,6 +218,14 @@ pub struct ModelDeploymentCard {
217218
#[serde(default)]
218219
pub runtime_config: ModelRuntimeConfig,
219220

221+
/// Media decoding configuration
222+
#[serde(default)]
223+
pub media_decoder: Option<MediaDecoder>,
224+
225+
/// Media fetching configuration
226+
#[serde(default)]
227+
pub media_fetcher: Option<MediaFetcher>,
228+
220229
#[serde(skip, default)]
221230
checksum: OnceLock<String>,
222231
}
@@ -520,6 +529,8 @@ impl ModelDeploymentCard {
520529
model_input: Default::default(), // set later
521530
user_data: None,
522531
runtime_config: ModelRuntimeConfig::default(),
532+
media_decoder: None,
533+
media_fetcher: None,
523534
checksum: OnceLock::new(),
524535
})
525536
}

lib/llm/src/preprocessor/media.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ mod loader;
77

88
pub use common::EncodedMediaData;
99
pub use decoders::{Decoder, ImageDecoder, MediaDecoder};
10-
pub use loader::MediaLoader;
10+
pub use loader::{MediaFetcher, MediaLoader};

0 commit comments

Comments
 (0)