Skip to main content

pagesift_ocr/
lcnet.rs

1//! PP-LCNetV4 骨干(检测、识别共用)。结构照 transformers 的 `modeling_pp_lcnet_v4.py`
2//! (Apache-2.0,PaddlePaddle 与 HuggingFace):大 stem + 4 段深度可分离卷积块。
3//! 批归一化在载入时并进卷积。
4
5use pagesift_infer::exec::{Cpu, Exec};
6use pagesift_infer::{Activation, BN_EPS, BatchNorm, Conv2d, Tensor, Weights};
7
8/// 每个块的配置:(核大小, 输入通道, 输出通道, 步长 (行, 列), 是否用 SE),对应 config.json 的 `block_configs`。
9pub type BlockConfig = (usize, usize, usize, (usize, usize), bool);
10
11/// 骨干的配置(config.json 里的 `backbone_config`)。
12#[derive(Debug, Clone)]
13pub struct Config {
14    pub stem_channels: [usize; 3],
15    pub blocks: Vec<Vec<BlockConfig>>,
16}
17
18impl Config {
19    /// 从 config.json 的 `backbone_config` 读。只支持 `stem_type = "large"`、缩放 1.0、
20    /// 不用可学习仿射块(PP-OCRv6 的检测、识别都是这样)。
21    pub fn from_json(v: &serde_json::Value) -> Result<Config, String> {
22        let b = &v["backbone_config"];
23        if b["stem_type"].as_str().unwrap_or("large") != "large" {
24            return Err("only the large stem is supported".into());
25        }
26        if b["use_learnable_affine_block"].as_bool().unwrap_or(false) {
27            return Err("learnable affine blocks are not supported".into());
28        }
29        let int = |x: &serde_json::Value| {
30            x.as_u64()
31                .map(|v| v as usize)
32                .ok_or("expected an integer in the config")
33        };
34        let sc = b["stem_channels"]
35            .as_array()
36            .ok_or("missing stem_channels")?;
37        let stem_channels = [int(&sc[0])?, int(&sc[1])?, int(&sc[2])?];
38        let mut blocks = Vec::new();
39        for stage in b["block_configs"]
40            .as_array()
41            .ok_or("missing block_configs")?
42        {
43            let mut s = Vec::new();
44            for blk in stage.as_array().ok_or("malformed block_configs")? {
45                let a = blk.as_array().ok_or("malformed block_configs")?;
46                // 步长可以是一个数,也可以是 [行, 列]
47                let stride = match a[3].as_array() {
48                    Some(p) => (int(&p[0])?, int(&p[1])?),
49                    None => (int(&a[3])?, int(&a[3])?),
50                };
51                s.push((
52                    int(&a[0])?,
53                    int(&a[1])?,
54                    int(&a[2])?,
55                    stride,
56                    a[4].as_bool().unwrap_or(false),
57                ));
58            }
59            blocks.push(s);
60        }
61        Ok(Config {
62            stem_channels,
63            blocks,
64        })
65    }
66}
67
68/// 卷积 + 批归一化(已并入)+ 激活。
69#[derive(Debug, Clone)]
70pub struct ConvBn {
71    pub conv: Conv2d,
72    pub act: Activation,
73}
74
75impl ConvBn {
76    /// 读 `{p}.convolution` 和 `{p}.normalization`;补边按 PyTorch 的 `(k − 1) / 2`。
77    pub fn load(
78        w: &Weights,
79        p: &str,
80        stride: (usize, usize),
81        groups: usize,
82        act: Activation,
83    ) -> Result<ConvBn, String> {
84        let k = w.get(&format!("{p}.convolution.weight"))?.shape[2];
85        let pad = (k - 1) / 2;
86        let mut conv = Conv2d::load(
87            w,
88            &format!("{p}.convolution"),
89            stride,
90            (pad, pad),
91            (1, 1),
92            groups,
93        )?;
94        conv.fold(&BatchNorm::load(w, &format!("{p}.normalization"), BN_EPS)?);
95        Ok(ConvBn { conv, act })
96    }
97
98    pub fn forward(&self, x: &Tensor) -> Tensor {
99        self.forward_on(&Cpu, x)
100    }
101
102    pub fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
103        let mut y = e.conv(&self.conv, x);
104        e.act(self.act, &mut y);
105        y
106    }
107}
108
109/// SE 注意力:全局平均 → 1×1 卷积 → ReLU → 1×1 卷积 → Hardsigmoid → 逐通道相乘。
110#[derive(Debug, Clone)]
111struct Se {
112    c1: Conv2d,
113    c2: Conv2d,
114}
115
116impl Se {
117    fn load(w: &Weights, p: &str) -> Result<Se, String> {
118        // [协议] ModuleList 里卷积和激活交替放,卷积在下标 0、2
119        let c = |i: usize| {
120            Conv2d::load(
121                w,
122                &format!("{p}.convolutions.{i}"),
123                (1, 1),
124                (0, 0),
125                (1, 1),
126                1,
127            )
128        };
129        Ok(Se {
130            c1: c(0)?,
131            c2: c(2)?,
132        })
133    }
134
135    fn forward_on<E: Exec>(&self, e: &E, x: &mut E::T) {
136        let pooled = e.global_avg(x);
137        let mut s = e.conv(&self.c1, &pooled);
138        e.act(Activation::Relu, &mut s);
139        let mut s = e.conv(&self.c2, &s);
140        e.act(Activation::HardSigmoid, &mut s);
141        e.scale_channels(x, &s);
142    }
143}
144
145/// 深度可分离卷积块。
146#[derive(Debug, Clone)]
147struct Block {
148    token: Conv2d,
149    se: Option<Se>,
150    ch1: ConvBn,
151    ch2: ConvBn,
152    residual: bool,
153}
154
155impl Block {
156    fn load(w: &Weights, p: &str, cfg: &BlockConfig) -> Result<Block, String> {
157        let &(k, cin, cout, stride, use_se) = cfg;
158        let same = cin == cout && stride == (1, 1);
159        // 步长 1、通道不变时 token_conv 是带偏置的普通深度卷积(重参数化后的形式);
160        // 否则是卷积 + 批归一化、不加激活
161        let token = if same {
162            Conv2d::load(
163                w,
164                &format!("{p}.token_conv"),
165                stride,
166                (k / 2, k / 2),
167                (1, 1),
168                cin,
169            )?
170        } else {
171            ConvBn::load(
172                w,
173                &format!("{p}.token_conv"),
174                stride,
175                cin,
176                Activation::Identity,
177            )?
178            .conv
179        };
180        let se = if use_se {
181            Some(Se::load(w, &format!("{p}.token_squeeze_excitation"))?)
182        } else {
183            None
184        };
185        Ok(Block {
186            token,
187            se,
188            ch1: ConvBn::load(
189                w,
190                &format!("{p}.channel_conv1"),
191                (1, 1),
192                1,
193                Activation::Gelu,
194            )?,
195            ch2: ConvBn::load(
196                w,
197                &format!("{p}.channel_conv2"),
198                (1, 1),
199                1,
200                Activation::Identity,
201            )?,
202            residual: same,
203        })
204    }
205
206    fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
207        let mut t = e.conv(&self.token, x);
208        if let Some(se) = &self.se {
209            se.forward_on(e, &mut t);
210        }
211        let y = self.ch2.forward_on(e, &self.ch1.forward_on(e, &t));
212        if self.residual { e.add(&t, &y) } else { y }
213    }
214}
215
216/// 整个骨干。
217#[derive(Debug, Clone)]
218pub struct Backbone {
219    stem1: ConvBn,
220    stem2a: ConvBn,
221    stem2b: ConvBn,
222    stem3: ConvBn,
223    stem4: ConvBn,
224    stages: Vec<Vec<Block>>,
225}
226
227impl Backbone {
228    /// `p` 是权重名前缀(PP-OCRv6 里是 `model.backbone.encoder`)。
229    pub fn load(w: &Weights, p: &str, cfg: &Config) -> Result<Backbone, String> {
230        // [协议] stem 的步长 (2, 1, 1, 2, 1)、激活 ReLU(PPLCNetV4Config 的缺省)
231        let s = |name: &str, stride: usize| {
232            ConvBn::load(
233                w,
234                &format!("{p}.convolution.{name}"),
235                (stride, stride),
236                1,
237                Activation::Relu,
238            )
239        };
240        let mut stages = Vec::new();
241        for (i, stage) in cfg.blocks.iter().enumerate() {
242            let mut v = Vec::new();
243            for (j, b) in stage.iter().enumerate() {
244                v.push(Block::load(w, &format!("{p}.blocks.{i}.blocks.{j}"), b)?);
245            }
246            stages.push(v);
247        }
248        Ok(Backbone {
249            stem1: s("stem1", 2)?,
250            stem2a: s("stem2a", 1)?,
251            stem2b: s("stem2b", 1)?,
252            stem3: s("stem3", 2)?,
253            stem4: s("stem4", 1)?,
254            stages,
255        })
256    }
257
258    /// 输入 `[3, H, W]`(已归一化),返回 4 段各自的输出(stage1–stage4)。
259    pub fn forward(&self, x: &Tensor) -> Vec<Tensor> {
260        self.forward_on(&Cpu, x)
261    }
262
263    /// 同 [`Backbone::forward`],在执行器 `ex` 上算。
264    pub fn forward_on<E: Exec>(&self, ex: &E, x: &E::T) -> Vec<E::T> {
265        let stem = self.stem_on(ex, x);
266        let mut outs: Vec<E::T> = Vec::with_capacity(self.stages.len());
267        for stage in &self.stages {
268            let h = self.stage_on(ex, stage, outs.last().unwrap_or(&stem));
269            outs.push(h);
270        }
271        outs
272    }
273
274    /// 只要最后一段的输出(识别用)。中间特征算完就释放,显存峰值低得多。
275    pub fn forward_last_on<E: Exec>(&self, ex: &E, x: &E::T) -> E::T {
276        let mut h = self.stem_on(ex, x);
277        for stage in &self.stages {
278            h = self.stage_on(ex, stage, &h);
279        }
280        h
281    }
282
283    fn stem_on<E: Exec>(&self, ex: &E, x: &E::T) -> E::T {
284        let e = ex.pad_right_bottom(&self.stem1.forward_on(ex, x), 1, 1);
285        let a = ex.pad_right_bottom(&self.stem2a.forward_on(ex, &e), 1, 1);
286        let a = self.stem2b.forward_on(ex, &a);
287        let pooled = ex.max_pool2d(&e, (2, 2), (1, 1), true);
288        let cat = ex.concat(&[&pooled, &a]);
289        self.stem4.forward_on(ex, &self.stem3.forward_on(ex, &cat))
290    }
291
292    fn stage_on<E: Exec>(&self, ex: &E, stage: &[Block], x: &E::T) -> E::T {
293        let mut h = stage[0].forward_on(ex, x);
294        for b in &stage[1..] {
295            h = b.forward_on(ex, &h);
296        }
297        h
298    }
299}