1pub use super::{anchor::AnchorGrid, assign::TaskAlignedAssigner};
30
31pub fn ciou_forward_cpu(pred: &[f32], target: &[f32], n: usize)
33 -> (Vec<f32>, Vec<f32>, Vec<f32>)
34{
35 const EPS: f32 = 1e-7;
36 let pi2 = std::f32::consts::PI * std::f32::consts::PI;
37 let mut iou = vec![0.0f32; n];
38 let mut v_vec = vec![0.0f32; n];
39 let mut alpha = vec![0.0f32; n];
40
41 for i in 0..n {
42 let px = pred[i]; let py = pred[n + i];
43 let pw = pred[2 * n + i]; let ph = pred[3 * n + i];
44 let tx = target[i]; let ty = target[n + i];
45 let tw = target[2 * n + i]; let th = target[3 * n + i];
46
47 let px1 = px - pw * 0.5; let px2 = px + pw * 0.5;
48 let py1 = py - ph * 0.5; let py2 = py + ph * 0.5;
49 let tx1 = tx - tw * 0.5; let tx2 = tx + tw * 0.5;
50 let ty1 = ty - th * 0.5; let ty2 = ty + th * 0.5;
51
52 let inter = ((px2.min(tx2) - px1.max(tx1)).max(0.0))
53 * ((py2.min(ty2) - py1.max(ty1)).max(0.0));
54 let union = pw * ph + tw * th - inter;
55 let iou_i = inter / (union + EPS);
56
57 let v_i = (4.0 / pi2)
58 * ((tw / (th + EPS)).atan() - (pw / (ph + EPS)).atan()).powi(2);
59 let alpha_i = v_i / (1.0 - iou_i + v_i + EPS);
60
61 iou[i] = iou_i; v_vec[i] = v_i; alpha[i] = alpha_i;
62 }
63 (iou, v_vec, alpha)
64}
65
66#[cfg(feature = "cuda")]
69mod cuda_impl {
70 use super::*;
71 use teeny_compiler::compiler::{driver::cuda::compile_kernel, target::cuda::Target};
72 use teeny_core::device::{Device, buffer::Buffer};
73 use teeny_cuda::{compiler::target::Capability, device::CudaDevice, errors::Result, testing};
74 use crate::models::yolo::kernels::loss::{
75 ciou::{YoloCiouLossBackward, YoloCiouLossForward},
76 cls::YoloBceClsLossBackward,
77 };
78
79 pub struct Yolo26Loss {
81 pub grid: AnchorGrid,
83 pub assigner: TaskAlignedAssigner,
85 pub assigner_o2o: TaskAlignedAssigner,
87 pub nc: usize,
89 pub cap: Capability,
91 block_n: i32,
92 }
93
94 impl Yolo26Loss {
95 pub fn new(img_h: usize, img_w: usize, nc: usize, cap: Capability) -> Self {
97 Self {
98 grid: AnchorGrid::yolo26(img_h, img_w),
99 assigner: TaskAlignedAssigner::default(),
100 assigner_o2o: TaskAlignedAssigner { top_k: 1, ..TaskAlignedAssigner::default() },
101 nc, cap, block_n: 64,
102 }
103 }
104
105 pub fn compute_grads(
112 &self,
113 device: &CudaDevice<'_>,
114 boxes: &[f32],
115 scores: &[f32],
116 gt_boxes_b: &[Vec<[f32; 4]>],
117 gt_cls_b: &[Vec<usize>],
118 ) -> anyhow::Result<(Vec<f32>, Vec<f32>)> {
119 let target = Target::new(self.cap);
120 let bn = self.block_n;
121 let ciou_fwd_ptx = std::fs::read(compile_kernel(&YoloCiouLossForward::<f32>::new(bn), &target, true)?)?;
122 let ciou_bwd_ptx = std::fs::read(compile_kernel(&YoloCiouLossBackward::<f32>::new(bn), &target, true)?)?;
123 let cls_bwd_ptx = std::fs::read(compile_kernel(&YoloBceClsLossBackward::<f32>::new(bn), &target, true)?)?;
124 let prog_ciou_fwd = testing::load_program_from_ptx::<YoloCiouLossForward<f32>>(&ciou_fwd_ptx)?;
125 let prog_ciou_bwd = testing::load_program_from_ptx::<YoloCiouLossBackward<f32>>(&ciou_bwd_ptx)?;
126 let prog_cls_bwd = testing::load_program_from_ptx::<YoloBceClsLossBackward<f32>>(&cls_bwd_ptx)?;
127 self.compute_grads_for_head(
128 device, &prog_ciou_fwd, &prog_ciou_bwd, &prog_cls_bwd,
129 &self.assigner, boxes, scores, gt_boxes_b, gt_cls_b, 1.0,
130 )
131 }
132
133 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
145 pub fn compute_grads_dual(
146 &self,
147 device: &CudaDevice<'_>,
148 boxes_o2m: &[f32],
149 scores_o2m: &[f32],
150 boxes_o2o: &[f32],
151 scores_o2o: &[f32],
152 gt_boxes_b: &[Vec<[f32; 4]>],
153 gt_cls_b: &[Vec<usize>],
154 w_o2m: f32,
155 w_o2o: f32,
156 ) -> anyhow::Result<(Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>)> {
157 let target = Target::new(self.cap);
158 let bn = self.block_n;
159 let ciou_fwd_ptx = std::fs::read(compile_kernel(&YoloCiouLossForward::<f32>::new(bn), &target, true)?)?;
160 let ciou_bwd_ptx = std::fs::read(compile_kernel(&YoloCiouLossBackward::<f32>::new(bn), &target, true)?)?;
161 let cls_bwd_ptx = std::fs::read(compile_kernel(&YoloBceClsLossBackward::<f32>::new(bn), &target, true)?)?;
162 let prog_ciou_fwd = testing::load_program_from_ptx::<YoloCiouLossForward<f32>>(&ciou_fwd_ptx)?;
163 let prog_ciou_bwd = testing::load_program_from_ptx::<YoloCiouLossBackward<f32>>(&ciou_bwd_ptx)?;
164 let prog_cls_bwd = testing::load_program_from_ptx::<YoloBceClsLossBackward<f32>>(&cls_bwd_ptx)?;
165
166 let (d_boxes_o2m, d_scores_o2m) = self.compute_grads_for_head(
167 device, &prog_ciou_fwd, &prog_ciou_bwd, &prog_cls_bwd,
168 &self.assigner, boxes_o2m, scores_o2m, gt_boxes_b, gt_cls_b, w_o2m,
169 )?;
170 let (d_boxes_o2o, d_scores_o2o) = self.compute_grads_for_head(
171 device, &prog_ciou_fwd, &prog_ciou_bwd, &prog_cls_bwd,
172 &self.assigner_o2o, boxes_o2o, scores_o2o, gt_boxes_b, gt_cls_b, w_o2o,
173 )?;
174 Ok((d_boxes_o2m, d_scores_o2m, d_boxes_o2o, d_scores_o2o))
175 }
176
177 #[allow(clippy::too_many_arguments)]
178 fn compute_grads_for_head(
179 &self,
180 device: &CudaDevice<'_>,
181 prog_ciou_fwd: &teeny_cuda::device::program::CudaProgram<'_, YoloCiouLossForward<f32>>,
182 prog_ciou_bwd: &teeny_cuda::device::program::CudaProgram<'_, YoloCiouLossBackward<f32>>,
183 prog_cls_bwd: &teeny_cuda::device::program::CudaProgram<'_, YoloBceClsLossBackward<f32>>,
184 assigner: &TaskAlignedAssigner,
185 boxes: &[f32],
186 scores: &[f32],
187 gt_boxes_b: &[Vec<[f32; 4]>],
188 gt_cls_b: &[Vec<usize>],
189 loss_weight: f32,
190 ) -> anyhow::Result<(Vec<f32>, Vec<f32>)> {
191 let b = gt_boxes_b.len();
192 let a = self.grid.n_anchors;
193 let nc = self.nc;
194
195 assert_eq!(boxes.len(), b * 4 * a);
196 assert_eq!(scores.len(), b * nc * a);
197
198 let mut d_boxes_out = vec![0.0f32; b * 4 * a];
199 let mut d_scores_out = vec![0.0f32; b * nc * a];
200
201 for bi in 0..b {
202 let boxes_i = &boxes[bi * 4 * a .. (bi + 1) * 4 * a];
203 let scores_i = &scores[bi * nc * a .. (bi + 1) * nc * a];
204
205 let xywh = self.grid.decode_ltrb_to_xywh(boxes_i);
207
208 let assign = assigner.assign(
210 &xywh, scores_i,
211 &self.grid.cx, &self.grid.cy,
212 >_boxes_b[bi], >_cls_b[bi],
213 );
214 let n_pos: usize = assign.is_positive.iter().filter(|&&p| p).count();
215 let target_scores_sum: f32 = (0..a)
217 .filter(|&i| assign.is_positive[i])
218 .map(|i| assign.soft_target[i])
219 .sum::<f32>()
220 .max(1.0);
221 let norm = loss_weight / target_scores_sum;
222
223 let mut d_xywh = vec![0.0f32; 4 * a];
225 if n_pos > 0 {
226 let pos_idx: Vec<usize> = (0..a).filter(|&i| assign.is_positive[i]).collect();
227 let np = pos_idx.len();
228
229 let mut pred_pos = vec![0.0f32; 4 * np];
230 let mut target_pos = vec![0.0f32; 4 * np];
231 for (j, &i) in pos_idx.iter().enumerate() {
232 for ch in 0..4 {
233 pred_pos[ch * np + j] = xywh[ch * a + i];
234 target_pos[ch * np + j] = assign.target_boxes[ch * a + i];
235 }
236 }
237
238 let (iou, v, alpha) = self.ciou_fwd_gpu(
240 device, prog_ciou_fwd, &pred_pos, &target_pos, np
241 )?;
242
243 let dy_pos: Vec<f32> = pos_idx.iter()
245 .map(|&i| norm * assign.soft_target[i])
246 .collect();
247
248 let d_pred_pos = self.ciou_bwd_gpu(
249 device, prog_ciou_bwd,
250 &dy_pos, &pred_pos, &target_pos, &iou, &v, &alpha, np,
251 )?;
252
253 for (j, &i) in pos_idx.iter().enumerate() {
254 for ch in 0..4 { d_xywh[ch * a + i] = d_pred_pos[ch * np + j]; }
255 }
256 }
257
258 let d_ltrb = self.grid.decode_backward(&d_xywh);
260 d_boxes_out[bi * 4 * a .. (bi + 1) * 4 * a].copy_from_slice(&d_ltrb);
261
262 let mut cls_target = vec![0.0f32; nc * a];
267 for (i, &pos) in assign.is_positive.iter().enumerate() {
268 if pos {
269 let c = assign.target_cls[i];
270 if c < nc { cls_target[c * a + i] = assign.soft_target[i]; }
271 }
272 }
273 let dy_cls = vec![norm; a];
274
275 let d_scores_i = self.cls_bwd_gpu(
276 device, prog_cls_bwd,
277 &dy_cls, scores_i, &cls_target, a, nc,
278 )?;
279 d_scores_out[bi * nc * a .. (bi + 1) * nc * a].copy_from_slice(&d_scores_i);
280 }
281
282 Ok((d_boxes_out, d_scores_out))
283 }
284
285 fn ciou_fwd_gpu(
288 &self, device: &CudaDevice<'_>,
289 prog: &teeny_cuda::device::program::CudaProgram<'_, YoloCiouLossForward<f32>>,
290 pred: &[f32], target: &[f32], n: usize,
291 ) -> Result<(Vec<f32>, Vec<f32>, Vec<f32>)> {
292 let mut pred_buf = device.buffer::<f32>(4 * n)?;
293 let mut target_buf = device.buffer::<f32>(4 * n)?;
294 let loss_buf = device.buffer::<f32>(n)?;
295 let iou_buf = device.buffer::<f32>(n)?;
296 let v_buf = device.buffer::<f32>(n)?;
297 let alpha_buf = device.buffer::<f32>(n)?;
298 pred_buf.to_device(pred)?;
299 target_buf.to_device(target)?;
300 let cfg = testing::launch_config_with_grid(n.div_ceil(self.block_n as usize), prog);
301 device.launch(prog, &cfg, (
302 pred_buf.as_device_ptr() as *mut f32,
303 target_buf.as_device_ptr() as *mut f32,
304 loss_buf.as_device_ptr() as *mut f32,
305 iou_buf.as_device_ptr() as *mut f32,
306 v_buf.as_device_ptr() as *mut f32,
307 alpha_buf.as_device_ptr() as *mut f32,
308 n as i32,
309 ))?;
310 let (mut iou, mut v, mut alpha) = (vec![0.0f32; n], vec![0.0f32; n], vec![0.0f32; n]);
311 iou_buf.to_host(&mut iou)?;
312 v_buf.to_host(&mut v)?;
313 alpha_buf.to_host(&mut alpha)?;
314 Ok((iou, v, alpha))
315 }
316
317 #[allow(clippy::too_many_arguments)]
318 fn ciou_bwd_gpu(
319 &self, device: &CudaDevice<'_>,
320 prog: &teeny_cuda::device::program::CudaProgram<'_, YoloCiouLossBackward<f32>>,
321 dy: &[f32], pred: &[f32], target: &[f32],
322 iou: &[f32], v: &[f32], alpha: &[f32], n: usize,
323 ) -> Result<Vec<f32>> {
324 let mut dy_buf = device.buffer::<f32>(n)?;
325 let mut pred_buf = device.buffer::<f32>(4 * n)?;
326 let mut target_buf = device.buffer::<f32>(4 * n)?;
327 let mut iou_buf = device.buffer::<f32>(n)?;
328 let mut v_buf = device.buffer::<f32>(n)?;
329 let mut alpha_buf = device.buffer::<f32>(n)?;
330 let d_pred_buf = device.buffer::<f32>(4 * n)?;
331 dy_buf.to_device(dy)?; pred_buf.to_device(pred)?; target_buf.to_device(target)?;
332 iou_buf.to_device(iou)?; v_buf.to_device(v)?; alpha_buf.to_device(alpha)?;
333 let cfg = testing::launch_config_with_grid(n.div_ceil(self.block_n as usize), prog);
334 device.launch(prog, &cfg, (
335 dy_buf.as_device_ptr() as *mut f32,
336 pred_buf.as_device_ptr() as *mut f32,
337 target_buf.as_device_ptr() as *mut f32,
338 iou_buf.as_device_ptr() as *mut f32,
339 v_buf.as_device_ptr() as *mut f32,
340 alpha_buf.as_device_ptr() as *mut f32,
341 d_pred_buf.as_device_ptr() as *mut f32,
342 n as i32,
343 ))?;
344 let mut out = vec![0.0f32; 4 * n];
345 d_pred_buf.to_host(&mut out)?;
346 Ok(out)
347 }
348
349 #[allow(clippy::too_many_arguments)]
350 fn cls_bwd_gpu(
351 &self, device: &CudaDevice<'_>,
352 prog: &teeny_cuda::device::program::CudaProgram<'_, YoloBceClsLossBackward<f32>>,
353 dy: &[f32], pred: &[f32], target: &[f32],
354 n: usize, c: usize,
355 ) -> Result<Vec<f32>> {
356 let mut dy_buf = device.buffer::<f32>(n)?;
357 let mut pred_buf = device.buffer::<f32>(c * n)?;
358 let mut target_buf = device.buffer::<f32>(c * n)?;
359 let d_pred_buf = device.buffer::<f32>(c * n)?;
360 dy_buf.to_device(dy)?; pred_buf.to_device(pred)?; target_buf.to_device(target)?;
361 let cfg = testing::launch_config_with_grid(n.div_ceil(self.block_n as usize), prog);
362 device.launch(prog, &cfg, (
363 dy_buf.as_device_ptr() as *mut f32,
364 pred_buf.as_device_ptr() as *mut f32,
365 target_buf.as_device_ptr() as *mut f32,
366 d_pred_buf.as_device_ptr() as *mut f32,
367 n as i32,
368 c as i32,
369 ))?;
370 let mut out = vec![0.0f32; c * n];
371 d_pred_buf.to_host(&mut out)?;
372 Ok(out)
373 }
374 }
375}
376
377#[cfg(feature = "cuda")]
378pub use cuda_impl::Yolo26Loss;