Skip to main content

rlx_florence2/
config.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Florence-2 configuration (DaViT vision backbone + BART text model).
17//!
18//! Mirrors `configuration_florence2.py`. The `large` preset matches
19//! [`microsoft/Florence-2-large`](https://huggingface.co/microsoft/Florence-2-large).
20
21use anyhow::{Context, Result};
22use serde::Deserialize;
23use std::path::Path;
24
25/// LayerNorm epsilon — PyTorch `nn.LayerNorm` default.
26pub const LN_EPS: f32 = 1e-5;
27
28/// ImageNet-style normalization used by the CLIP image processor.
29pub const IMAGE_MEAN: [f32; 3] = [0.485, 0.456, 0.406];
30pub const IMAGE_STD: [f32; 3] = [0.229, 0.224, 0.225];
31
32/// DaViT vision backbone configuration (`vision_config`).
33#[derive(Debug, Clone)]
34pub struct Florence2VisionConfig {
35    pub dim_embed: Vec<usize>,
36    pub depths: Vec<usize>,
37    pub num_heads: Vec<usize>,
38    pub num_groups: Vec<usize>,
39    pub patch_size: Vec<usize>,
40    pub patch_stride: Vec<usize>,
41    pub patch_padding: Vec<usize>,
42    pub patch_prenorm: Vec<bool>,
43    pub window_size: usize,
44    pub projection_dim: usize,
45    pub mlp_ratio: f64,
46    /// `image_pos_embed.max_pos_embeddings`.
47    pub image_pos_embed_max: usize,
48    /// `visual_temporal_embedding.max_temporal_embeddings`.
49    pub visual_temporal_max: usize,
50    /// Ordered list of `spatial_avg_pool` / `temporal_avg_pool` / `last_frame`.
51    pub image_feature_source: Vec<String>,
52}
53
54impl Florence2VisionConfig {
55    pub fn num_stages(&self) -> usize {
56        self.dim_embed.len()
57    }
58    /// Output channel dim of the backbone (`dim_embed[-1]`).
59    pub fn dim_out(&self) -> usize {
60        *self.dim_embed.last().unwrap()
61    }
62}
63
64/// BART text model configuration (`text_config`).
65#[derive(Debug, Clone)]
66pub struct Florence2TextConfig {
67    pub vocab_size: usize,
68    pub d_model: usize,
69    pub encoder_layers: usize,
70    pub decoder_layers: usize,
71    pub encoder_attention_heads: usize,
72    pub decoder_attention_heads: usize,
73    pub encoder_ffn_dim: usize,
74    pub decoder_ffn_dim: usize,
75    pub max_position_embeddings: usize,
76    pub scale_embedding: bool,
77    pub bos_token_id: u32,
78    pub eos_token_id: u32,
79    pub pad_token_id: u32,
80    pub decoder_start_token_id: u32,
81    pub forced_bos_token_id: u32,
82    pub forced_eos_token_id: u32,
83    pub num_beams: usize,
84    pub no_repeat_ngram_size: usize,
85}
86
87impl Florence2TextConfig {
88    /// Learned positional embeddings carry an offset of 2 (BART hack).
89    pub const POS_OFFSET: usize = 2;
90    pub fn enc_head_dim(&self) -> usize {
91        self.d_model / self.encoder_attention_heads
92    }
93    pub fn dec_head_dim(&self) -> usize {
94        self.d_model / self.decoder_attention_heads
95    }
96}
97
98/// Full Florence-2 configuration.
99#[derive(Debug, Clone)]
100pub struct Florence2Config {
101    pub vision: Florence2VisionConfig,
102    pub text: Florence2TextConfig,
103    pub vocab_size: usize,
104    pub projection_dim: usize,
105}
106
107impl Florence2Config {
108    /// `microsoft/Florence-2-large` preset.
109    pub fn large() -> Self {
110        Self {
111            vision: Florence2VisionConfig {
112                dim_embed: vec![256, 512, 1024, 2048],
113                depths: vec![1, 1, 9, 1],
114                num_heads: vec![8, 16, 32, 64],
115                num_groups: vec![8, 16, 32, 64],
116                patch_size: vec![7, 3, 3, 3],
117                patch_stride: vec![4, 2, 2, 2],
118                patch_padding: vec![3, 1, 1, 1],
119                patch_prenorm: vec![false, true, true, true],
120                window_size: 12,
121                projection_dim: 1024,
122                mlp_ratio: 4.0,
123                image_pos_embed_max: 50,
124                visual_temporal_max: 100,
125                image_feature_source: vec!["spatial_avg_pool".into(), "temporal_avg_pool".into()],
126            },
127            text: Florence2TextConfig {
128                vocab_size: 51289,
129                d_model: 1024,
130                encoder_layers: 12,
131                decoder_layers: 12,
132                encoder_attention_heads: 16,
133                decoder_attention_heads: 16,
134                encoder_ffn_dim: 4096,
135                decoder_ffn_dim: 4096,
136                max_position_embeddings: 4096,
137                scale_embedding: false,
138                bos_token_id: 0,
139                eos_token_id: 2,
140                pad_token_id: 1,
141                decoder_start_token_id: 2,
142                forced_bos_token_id: 0,
143                forced_eos_token_id: 2,
144                num_beams: 3,
145                no_repeat_ngram_size: 3,
146            },
147            vocab_size: 51289,
148            projection_dim: 1024,
149        }
150    }
151
152    /// Parse an HF `config.json`.
153    pub fn from_hf_config_json(path: &Path) -> Result<Self> {
154        let raw = std::fs::read_to_string(path)
155            .with_context(|| format!("read florence2 config {}", path.display()))?;
156        let hf: HfConfig = serde_json::from_str(&raw)
157            .with_context(|| format!("parse florence2 config {}", path.display()))?;
158        Ok(hf.into_config())
159    }
160
161    /// Embedding scale (`sqrt(d_model)` when `scale_embedding`, else 1).
162    pub fn embed_scale(&self) -> f32 {
163        if self.text.scale_embedding {
164            (self.text.d_model as f32).sqrt()
165        } else {
166            1.0
167        }
168    }
169}
170
171// ---- HF config.json deserialization ----
172
173#[derive(Debug, Deserialize)]
174struct HfConfig {
175    vocab_size: usize,
176    projection_dim: usize,
177    text_config: HfTextConfig,
178    vision_config: HfVisionConfig,
179}
180
181#[derive(Debug, Deserialize)]
182struct HfTextConfig {
183    vocab_size: usize,
184    d_model: usize,
185    encoder_layers: usize,
186    decoder_layers: usize,
187    encoder_attention_heads: usize,
188    decoder_attention_heads: usize,
189    encoder_ffn_dim: usize,
190    decoder_ffn_dim: usize,
191    max_position_embeddings: usize,
192    #[serde(default)]
193    scale_embedding: bool,
194    bos_token_id: u32,
195    eos_token_id: u32,
196    pad_token_id: u32,
197    decoder_start_token_id: u32,
198    forced_bos_token_id: u32,
199    forced_eos_token_id: u32,
200    #[serde(default = "default_beams")]
201    num_beams: usize,
202    #[serde(default = "default_ngram")]
203    no_repeat_ngram_size: usize,
204}
205
206fn default_beams() -> usize {
207    3
208}
209fn default_ngram() -> usize {
210    3
211}
212
213#[derive(Debug, Deserialize)]
214struct HfVisionConfig {
215    dim_embed: Vec<usize>,
216    depths: Vec<usize>,
217    num_heads: Vec<usize>,
218    num_groups: Vec<usize>,
219    patch_size: Vec<usize>,
220    patch_stride: Vec<usize>,
221    patch_padding: Vec<usize>,
222    patch_prenorm: Vec<bool>,
223    window_size: usize,
224    projection_dim: usize,
225    image_pos_embed: HfPosEmbed,
226    visual_temporal_embedding: HfTemporalEmbed,
227    image_feature_source: Vec<String>,
228}
229
230#[derive(Debug, Deserialize)]
231struct HfPosEmbed {
232    max_pos_embeddings: usize,
233}
234
235#[derive(Debug, Deserialize)]
236struct HfTemporalEmbed {
237    max_temporal_embeddings: usize,
238}
239
240impl HfConfig {
241    fn into_config(self) -> Florence2Config {
242        let v = self.vision_config;
243        let t = self.text_config;
244        Florence2Config {
245            vision: Florence2VisionConfig {
246                dim_embed: v.dim_embed,
247                depths: v.depths,
248                num_heads: v.num_heads,
249                num_groups: v.num_groups,
250                patch_size: v.patch_size,
251                patch_stride: v.patch_stride,
252                patch_padding: v.patch_padding,
253                patch_prenorm: v.patch_prenorm,
254                window_size: v.window_size,
255                projection_dim: v.projection_dim,
256                mlp_ratio: 4.0,
257                image_pos_embed_max: v.image_pos_embed.max_pos_embeddings,
258                visual_temporal_max: v.visual_temporal_embedding.max_temporal_embeddings,
259                image_feature_source: v.image_feature_source,
260            },
261            text: Florence2TextConfig {
262                vocab_size: t.vocab_size,
263                d_model: t.d_model,
264                encoder_layers: t.encoder_layers,
265                decoder_layers: t.decoder_layers,
266                encoder_attention_heads: t.encoder_attention_heads,
267                decoder_attention_heads: t.decoder_attention_heads,
268                encoder_ffn_dim: t.encoder_ffn_dim,
269                decoder_ffn_dim: t.decoder_ffn_dim,
270                max_position_embeddings: t.max_position_embeddings,
271                scale_embedding: t.scale_embedding,
272                bos_token_id: t.bos_token_id,
273                eos_token_id: t.eos_token_id,
274                pad_token_id: t.pad_token_id,
275                decoder_start_token_id: t.decoder_start_token_id,
276                forced_bos_token_id: t.forced_bos_token_id,
277                forced_eos_token_id: t.forced_eos_token_id,
278                num_beams: t.num_beams,
279                no_repeat_ngram_size: t.no_repeat_ngram_size,
280            },
281            vocab_size: self.vocab_size,
282            projection_dim: self.projection_dim,
283        }
284    }
285}
286
287/// Returns the post-processing type for a task token (e.g. `<CAPTION>` →
288/// `pure_text`, `<OD>` → `description_with_bboxes`).
289pub fn task_post_processing_type(task: &str) -> &'static str {
290    match task {
291        "<OCR>" => "pure_text",
292        "<OCR_WITH_REGION>" => "ocr",
293        "<CAPTION>" => "pure_text",
294        "<DETAILED_CAPTION>" => "pure_text",
295        "<MORE_DETAILED_CAPTION>" => "pure_text",
296        "<OD>" => "description_with_bboxes",
297        "<DENSE_REGION_CAPTION>" => "description_with_bboxes",
298        "<CAPTION_TO_PHRASE_GROUNDING>" => "phrase_grounding",
299        "<REFERRING_EXPRESSION_SEGMENTATION>" => "polygons",
300        "<REGION_TO_SEGMENTATION>" => "polygons",
301        "<OPEN_VOCABULARY_DETECTION>" => "description_with_bboxes_or_polygons",
302        "<REGION_TO_CATEGORY>" => "pure_text",
303        "<REGION_TO_DESCRIPTION>" => "pure_text",
304        "<REGION_TO_OCR>" => "pure_text",
305        "<REGION_PROPOSAL>" => "bboxes",
306        _ => "pure_text",
307    }
308}
309
310/// Expands a task token (optionally with an `input` suffix) into the prompt
311/// string fed to the BART tokenizer. Mirrors `_construct_prompts`.
312pub fn construct_prompt(text: &str) -> String {
313    // 1. Fixed task prompts without additional inputs.
314    for (token, prompt) in TASK_PROMPTS_WITHOUT_INPUTS {
315        if text.contains(token) {
316            // HF asserts the token is the only content.
317            return prompt.to_string();
318        }
319    }
320    // 2. Task prompts with additional inputs.
321    for (token, prompt) in TASK_PROMPTS_WITH_INPUT {
322        if text.contains(token) {
323            let input = text.replace(token, "");
324            return prompt.replace("{input}", &input);
325        }
326    }
327    text.to_string()
328}
329
330const TASK_PROMPTS_WITHOUT_INPUTS: &[(&str, &str)] = &[
331    ("<OCR>", "What is the text in the image?"),
332    (
333        "<OCR_WITH_REGION>",
334        "What is the text in the image, with regions?",
335    ),
336    ("<CAPTION>", "What does the image describe?"),
337    (
338        "<DETAILED_CAPTION>",
339        "Describe in detail what is shown in the image.",
340    ),
341    (
342        "<MORE_DETAILED_CAPTION>",
343        "Describe with a paragraph what is shown in the image.",
344    ),
345    (
346        "<OD>",
347        "Locate the objects with category name in the image.",
348    ),
349    (
350        "<DENSE_REGION_CAPTION>",
351        "Locate the objects in the image, with their descriptions.",
352    ),
353    (
354        "<REGION_PROPOSAL>",
355        "Locate the region proposals in the image.",
356    ),
357];
358
359const TASK_PROMPTS_WITH_INPUT: &[(&str, &str)] = &[
360    (
361        "<CAPTION_TO_PHRASE_GROUNDING>",
362        "Locate the phrases in the caption: {input}",
363    ),
364    (
365        "<REFERRING_EXPRESSION_SEGMENTATION>",
366        "Locate {input} in the image with mask",
367    ),
368    (
369        "<REGION_TO_SEGMENTATION>",
370        "What is the polygon mask of region {input}",
371    ),
372    (
373        "<OPEN_VOCABULARY_DETECTION>",
374        "Locate {input} in the image.",
375    ),
376    ("<REGION_TO_CATEGORY>", "What is the region {input}?"),
377    (
378        "<REGION_TO_DESCRIPTION>",
379        "What does the region {input} describe?",
380    ),
381    ("<REGION_TO_OCR>", "What text is in the region {input}?"),
382];