Skip to main content

pagesift_gpu/
exec.rs

1//! 显存里的批量执行器:实现 `pagesift_infer::exec::Exec`,模型的一次前向全程留在显存,只在两头传输。
2//!
3//! **拼批和结果一致。** 一批 N 个样本存成 `[N, C, H, W]`,W 是这批最宽的;每个句柄记着各样本的
4//! 有效宽度。CPU 单张推理时,卷积窗口越过右边界读到的是 0,所以这里每层卷积、池化之后把有效宽度以外
5//! 的列清零(`zero_cols`),下一层读到的就和单张时的补边完全一样;全局平均只算有效列;注意力的
6//! softmax 只在有效长度上做。这样拼批的结果和单张只差浮点舍入,而不是 PaddleOCR 式的“补到一样宽”
7//! (那种做法结果随同批的其他样本变化)。
8//!
9//! **出错不 panic。** 分配或启动失败后记下第一个错误,之后的算子只维护形状、不再动显存,
10//! 模型代码不用到处处理 `Result`,整批在 [`BatchExec::finish`] 时报告。
11//!
12//! **试算。** [`Ctx::dry`] 走同样的代码但不碰显存,只记峰值,批大小就是这样按空闲显存推出来的。
13
14use std::any::Any;
15use std::cell::{Cell, RefCell};
16use std::collections::HashMap;
17use std::rc::Rc;
18use std::sync::Arc;
19
20use pagesift_infer::{Conv2d, Tensor};
21
22use crate::Gpu;
23use crate::buffer::{Args, DeviceBuffer};
24use crate::driver::{DevPtr, Handle};
25use crate::ops::{BLOCK_1D, blocks_1d};
26use crate::pool::{OOM_TAG, Pool, bytes, capacity_class};
27
28/// 拥有权重的对象(模型)。权重缓存按地址查,缓存项持有它,地址在缓存存续期间就一直有效。
29pub type Owner = Arc<dyn Any + Send + Sync>;
30
31/// 一个权重缓存项。
32struct Cached<'g> {
33    buf: DeviceBuffer<'g>,
34    /// 只为保活。
35    _owner: Owner,
36}
37
38/// 工人线程上的长期状态:显存池和权重缓存。
39pub struct Ctx<'g> {
40    pub(crate) gpu: &'g Gpu,
41    pub(crate) pool: RefCell<Pool<'g>>,
42    weights: RefCell<HashMap<(usize, usize), Cached<'g>>>,
43    pub(crate) hashed: RefCell<HashMap<u64, DeviceBuffer<'g>>>,
44    weight_bytes: Cell<u64>,
45}
46
47impl<'g> Ctx<'g> {
48    pub fn new(gpu: &'g Gpu) -> Ctx<'g> {
49        Ctx {
50            gpu,
51            pool: RefCell::new(Pool::new()),
52            weights: RefCell::new(HashMap::new()),
53            hashed: RefCell::new(HashMap::new()),
54            weight_bytes: Cell::new(0),
55        }
56    }
57
58    pub fn gpu(&self) -> &'g Gpu {
59        self.gpu
60    }
61
62    /// 真正执行的批量执行器。`owner` 是拥有权重的模型。
63    pub fn exec<'c>(&'c self, n: usize, owner: Owner) -> BatchExec<'c, 'g> {
64        BatchExec::new(self, n, owner, false)
65    }
66
67    /// 试算:不碰显存,只记显存峰值。
68    pub fn dry<'c>(&'c self, n: usize, owner: Owner) -> BatchExec<'c, 'g> {
69        BatchExec::new(self, n, owner, true)
70    }
71
72    /// 自上次 [`Ctx::reset_peak`] 以来占用显存的峰值(缓冲池 + 权重,字节)。
73    pub fn peak_bytes(&self) -> u64 {
74        self.pool.borrow().peak() + self.weight_bytes.get()
75    }
76
77    /// 真正需要的显存峰值:在用的张量 + 权重(不含池里闲着的,字节)。
78    pub fn needed_bytes(&self) -> u64 {
79        self.pool.borrow().in_use_peak() + self.weight_bytes.get()
80    }
81
82    pub fn reset_peak(&self) {
83        self.pool.borrow_mut().reset_peak();
84    }
85
86    /// 现在能用于工作内存的显存:驱动报告的空闲量加上池里闲着的。
87    pub fn available_bytes(&self) -> Result<u64, String> {
88        Ok(self.gpu.memory_free()? + self.pool.borrow().idle())
89    }
90
91    /// 把权重传上显存(已在就直接返回),`Err` 时已经试过腾出缓冲池。
92    fn weight(
93        &self,
94        key: (usize, usize),
95        make: impl FnOnce() -> Vec<f32>,
96        owner: &Owner,
97    ) -> Result<DevPtr, String> {
98        if let Some(c) = self.weights.borrow().get(&key) {
99            return Ok(c.buf.ptr());
100        }
101        let data = make();
102        let buf = self.upload_or_trim(&data)?;
103        let ptr = buf.ptr();
104        self.count_weight(data.len());
105        self.weights.borrow_mut().insert(
106            key,
107            Cached {
108                buf,
109                _owner: owner.clone(),
110            },
111        );
112        Ok(ptr)
113    }
114
115    /// 记下又有 `len` 个 f32 的权重常驻显存。
116    pub(crate) fn count_weight(&self, len: usize) {
117        self.weight_bytes.set(self.weight_bytes.get() + bytes(len));
118    }
119
120    /// 上传;显存不够时腾掉缓冲池的空闲部分再试一次。
121    pub(crate) fn upload_or_trim(&self, data: &[f32]) -> Result<DeviceBuffer<'g>, String> {
122        self.gpu.upload(data).or_else(|_| {
123            self.pool.borrow_mut().trim();
124            self.gpu.upload(data).map_err(|e| {
125                format!(
126                    "{OOM_TAG}: uploading {} bytes of weights failed ({e})",
127                    bytes(data.len())
128                )
129            })
130        })
131    }
132}
133
134/// 试算时的计数。
135#[derive(Default)]
136struct DryStats {
137    live: Cell<u64>,
138    peak: Cell<u64>,
139}
140
141/// 显存里的一个批量张量。
142pub struct DevTensor<'c, 'g> {
143    /// 试算或出错后为 None。
144    buf: Option<DeviceBuffer<'g>>,
145    cap: usize,
146    pool: &'c RefCell<Pool<'g>>,
147    dry: Option<Rc<DryStats>>,
148    pub(crate) shape: Vec<usize>,
149    /// 每个样本的有效宽度(图像特征是 W,序列是 T)。
150    pub(crate) widths: Rc<Vec<u32>>,
151}
152
153impl DevTensor<'_, '_> {
154    /// 设备地址;试算或出错后是 0(这时不会启动内核)。
155    pub(crate) fn ptr(&self) -> DevPtr {
156        self.buf.as_ref().map_or(0, |b| b.ptr())
157    }
158}
159
160impl Drop for DevTensor<'_, '_> {
161    fn drop(&mut self) {
162        if let Some(b) = self.buf.take() {
163            self.pool.borrow_mut().give(self.cap, b);
164        }
165        if let Some(d) = &self.dry {
166            d.live.set(d.live.get() - bytes(self.cap));
167        }
168    }
169}
170
171/// 一次批量前向的执行器。一批一个,用完检查 [`BatchExec::finish`]。
172pub struct BatchExec<'c, 'g> {
173    pub(crate) ctx: &'c Ctx<'g>,
174    pub(crate) n: usize,
175    owner: Owner,
176    dry: Option<Rc<DryStats>>,
177    /// 试算时还不在显存里的权重字节数。
178    dry_weights: RefCell<HashMap<usize, u64>>,
179    err: RefCell<Option<String>>,
180    /// 各样本有效宽度上传后的显存,同样的宽度只传一次。
181    masks: RefCell<HashMap<Vec<u32>, DeviceBuffer<'g>>>,
182}
183
184impl Drop for BatchExec<'_, '_> {
185    fn drop(&mut self) {
186        // 这一批没用到的容量档不留:下一批宽度相近时还能复用,差得远的不再占着显存
187        if self.dry.is_none() {
188            self.ctx.pool.borrow_mut().end_batch();
189        }
190    }
191}
192
193impl<'c, 'g> BatchExec<'c, 'g> {
194    fn new(ctx: &'c Ctx<'g>, n: usize, owner: Owner, dry: bool) -> BatchExec<'c, 'g> {
195        BatchExec {
196            ctx,
197            n,
198            owner,
199            dry: dry.then(Rc::default),
200            dry_weights: RefCell::new(HashMap::new()),
201            err: RefCell::new(None),
202            masks: RefCell::new(HashMap::new()),
203        }
204    }
205
206    /// 还能不能真的动显存。
207    pub(crate) fn live(&self) -> bool {
208        self.dry.is_none() && self.err.borrow().is_none()
209    }
210
211    /// 记下第一个错误。
212    pub(crate) fn fail(&self, e: String) {
213        self.err.borrow_mut().get_or_insert(e);
214    }
215
216    pub(crate) fn check(&self, r: Result<(), String>) {
217        if let Err(e) = r {
218            self.fail(e);
219        }
220    }
221
222    /// 等已提交的工作跑完,报告这一批有没有出错。
223    pub fn finish(&self) -> Result<(), String> {
224        if self.live() {
225            self.check(self.ctx.gpu.synchronize());
226        }
227        match self.err.borrow().as_ref() {
228            Some(e) => Err(e.clone()),
229            None => Ok(()),
230        }
231    }
232
233    /// 试算的结果:(工作内存峰值, 还没上显存的权重),字节。真正执行时恒为 0。
234    pub fn dry_usage(&self) -> (u64, u64) {
235        let peak = self.dry.as_ref().map_or(0, |d| d.peak.get());
236        (peak, self.dry_weights.borrow().values().sum())
237    }
238
239    /// 分配一个张量。`len` 超过 u32 范围也当显存不足:内核的下标是 u32,减小批次就能跑。
240    pub(crate) fn alloc(
241        &self,
242        len: usize,
243        shape: Vec<usize>,
244        widths: Rc<Vec<u32>>,
245    ) -> DevTensor<'c, 'g> {
246        let cap = capacity_class(len);
247        if len > u32::MAX as usize {
248            self.fail(format!(
249                "{OOM_TAG}: tensor of {len} elements exceeds the kernels' u32 indices"
250            ));
251        }
252        let mut t = DevTensor {
253            buf: None,
254            cap,
255            pool: &self.ctx.pool,
256            dry: None,
257            shape,
258            widths,
259        };
260        if let Some(d) = &self.dry {
261            d.live.set(d.live.get() + bytes(cap));
262            d.peak.set(d.peak.get().max(d.live.get()));
263            t.dry = Some(d.clone());
264        } else if self.live() {
265            match self.ctx.pool.borrow_mut().take(self.ctx.gpu, cap) {
266                Ok(b) => t.buf = Some(b),
267                Err(e) => self.fail(e),
268            }
269        }
270        t
271    }
272
273    /// 权重的设备地址;试算时只累计大小。
274    pub(crate) fn weight(&self, data: &[f32]) -> DevPtr {
275        self.weight_keyed(data.as_ptr() as usize, data.len(), || data.to_vec())
276    }
277
278    /// 卷积权重重排成 `[kh, kw, o, c]`(隐式卷积用),缓存键是原权重地址加 1(f32 地址 4 字节对齐,不会撞)。
279    pub(crate) fn weight_kernel_first(&self, l: &Conv2d) -> DevPtr {
280        let w = &l.weight;
281        let (o, c, kh, kw) = (w.shape[0], w.shape[1], w.shape[2], w.shape[3]);
282        self.weight_keyed(w.data.as_ptr() as usize + 1, w.data.len(), || {
283            let mut out = vec![0f32; w.data.len()];
284            for ko in 0..kh * kw {
285                for oc in 0..o {
286                    for ic in 0..c {
287                        out[(ko * o + oc) * c + ic] = w.data[(oc * c + ic) * kh * kw + ko];
288                    }
289                }
290            }
291            out
292        })
293    }
294
295    fn weight_keyed(&self, addr: usize, len: usize, make: impl FnOnce() -> Vec<f32>) -> DevPtr {
296        let key = (addr, len);
297        if self.dry.is_some() {
298            if !self.ctx.weights.borrow().contains_key(&key) {
299                self.dry_weights.borrow_mut().insert(addr, bytes(len));
300            }
301            return 0;
302        }
303        if !self.live() {
304            return 0;
305        }
306        self.ctx.weight(key, make, &self.owner).unwrap_or_else(|e| {
307            self.fail(e);
308            0
309        })
310    }
311
312    /// 上传一个样本(批大小必须是 1),有效宽度就是实际宽度。
313    pub fn upload(&self, x: &Tensor) -> DevTensor<'c, 'g> {
314        assert_eq!(self.n, 1, "upload 只用于批大小 1,拼批用 upload_batch");
315        let width = *x.shape.last().expect("张量至少一维") as u32;
316        // 序列 [T, C] 的有效长度是 T
317        let width = if x.shape.len() == 2 {
318            x.shape[0] as u32
319        } else {
320            width
321        };
322        let t = self.alloc(x.data.len(), x.shape.clone(), Rc::new(vec![width]));
323        self.write(&t, &x.data);
324        t
325    }
326
327    /// 不带数据的张量(试算时当输入用);有效宽度取最后一维。
328    pub fn placeholder(&self, shape: Vec<usize>) -> DevTensor<'c, 'g> {
329        let width = *shape.last().expect("张量至少一维") as u32;
330        self.alloc(
331            self.n * shape.iter().product::<usize>(),
332            shape,
333            Rc::new(vec![width; self.n]),
334        )
335    }
336
337    /// 上传一批 `[C, H, w_i]`(C、H 相同),右边补 0 到最宽的宽度,并记下各样本的有效宽度。
338    pub fn upload_batch(&self, xs: &[&Tensor]) -> DevTensor<'c, 'g> {
339        assert_eq!(xs.len(), self.n, "样本数和批大小不同");
340        let (c, h, _) = xs[0].chw();
341        let widths: Vec<u32> = xs.iter().map(|x| x.chw().2 as u32).collect();
342        let wmax = *widths.iter().max().expect("空批") as usize;
343        let t = self.alloc(
344            self.n * c * h * wmax,
345            vec![c, h, wmax],
346            Rc::new(widths.clone()),
347        );
348        if self.live() {
349            let mut host = vec![0f32; self.n * c * h * wmax];
350            for (z, (x, &w)) in xs.iter().zip(&widths).enumerate() {
351                assert_eq!(x.chw().0, c, "同一批的通道数不同");
352                assert_eq!(x.chw().1, h, "同一批的高度不同");
353                let (w, base) = (w as usize, z * c * h * wmax);
354                for r in 0..c * h {
355                    let src = &x.data[r * w..(r + 1) * w];
356                    host[base + r * wmax..base + r * wmax + w].copy_from_slice(src);
357                }
358            }
359            self.write(&t, &host);
360        }
361        t
362    }
363
364    fn write(&self, t: &DevTensor<'_, 'g>, data: &[f32]) {
365        if let (true, Some(b)) = (self.live(), t.buf.as_ref()) {
366            self.check(b.write_prefix(data));
367        }
368    }
369
370    /// 下载整个批量张量(行优先,含补边);出错或试算时返回错误或空。
371    pub fn download(&self, t: &DevTensor<'_, 'g>) -> Result<Vec<f32>, String> {
372        self.finish()?;
373        let mut v = vec![0f32; self.n * t.shape.iter().product::<usize>()];
374        if let Some(b) = t.buf.as_ref() {
375            b.read_prefix(&mut v)?;
376        }
377        Ok(v)
378    }
379
380    /// 一维内核:`total` 个工作项,每块 256 线程。
381    pub(crate) fn d1(&self, f: Handle, total: usize, args: Args) {
382        if self.live() {
383            self.check(
384                self.ctx
385                    .gpu
386                    .launch(f, (blocks_1d(total), 1, 1), (BLOCK_1D, 1, 1), args),
387            );
388        }
389    }
390
391    /// 各样本有效宽度的设备地址。
392    pub(crate) fn widths_ptr(&self, w: &[u32]) -> DevPtr {
393        if !self.live() {
394            return 0;
395        }
396        if let Some(b) = self.masks.borrow().get(w) {
397            return b.ptr();
398        }
399        let bits: Vec<f32> = w.iter().map(|&v| f32::from_bits(v)).collect();
400        match self.ctx.upload_or_trim(&bits) {
401            Ok(b) => {
402                let p = b.ptr();
403                self.masks.borrow_mut().insert(w.to_vec(), b);
404                p
405            }
406            Err(e) => {
407                self.fail(e);
408                0
409            }
410        }
411    }
412
413    /// 把有效宽度以外的列清零(`x` 是 `[C, H, W]`)。各样本一样宽时什么也不做。
414    pub(crate) fn mask(&self, x: &DevTensor<'_, 'g>) {
415        let (c, h, w) = (x.shape[0], x.shape[1], x.shape[2]);
416        if x.widths.iter().all(|&v| v as usize == w) {
417            return;
418        }
419        let per = c * h * w;
420        let args = Args::default()
421            .raw_ptr(x.ptr())
422            .raw_ptr(self.widths_ptr(&x.widths))
423            .u32(per)
424            .u32(w)
425            .u32(self.n * per);
426        self.d1(self.ctx.gpu.k.zero_cols, self.n * per, args);
427    }
428}