1use crate::{
4 Error, Result,
5 inference::{Inference, ModelContext, PreparedModel},
6 loom::Specialization,
7 model::{Command, Dispatch, ModelFragment, ModelSession},
8 plan_cache::PlanCache,
9 tensor::{DType, DeviceTensor, Layout, TensorDesc},
10};
11use std::sync::Arc;
12mod affine;
13mod composite;
14mod normalize;
15mod resize;
16mod similarity;
17mod views;
18pub use affine::{RgbSampling, invert_affine};
19pub use composite::RgbComposite;
20pub use resize::RgbResize;
21pub use similarity::fit_similarity_2d;
22pub use views::RgbEncoding;
23
24type NormalizationKey = (TensorDesc, [u32; 3], [u32; 3]);
25
26pub struct ImageOps {
28 context: ModelContext,
29 patch_plans: PlanCache<(TensorDesc, usize), PreparedModel>,
30 normalize_plans: PlanCache<NormalizationKey, PreparedModel>,
31 affine_plans: PlanCache<(TensorDesc, usize, usize, usize, RgbSampling), PreparedModel>,
32 resize_plans: PlanCache<(TensorDesc, RgbResize), PreparedModel>,
33 similarity_plans: PlanCache<(TensorDesc, Vec<u32>), PreparedModel>,
34 view_plans: PlanCache<(TensorDesc, usize, RgbEncoding), PreparedModel>,
35 composite_plans: PlanCache<(TensorDesc, TensorDesc, RgbComposite), PreparedModel>,
36}
37impl ImageOps {
38 pub fn new(context: &ModelContext, cache_capacity: usize) -> Result<Self> {
40 Ok(Self {
41 context: context.clone(),
42 patch_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
43 normalize_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
44 affine_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
45 resize_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
46 similarity_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
47 view_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
48 composite_plans: PlanCache::new(cache_capacity, PreparedModel::is_idle)?,
49 })
50 }
51
52 pub fn prepare_patchify(&self, input: &TensorDesc, patch: usize) -> Result<Arc<PreparedModel>> {
56 self.patch_plans.get_or_prepare((input.clone(), patch), || {
57 self.patchify_fragment(input, patch)?.prepare(3)
58 })
59 }
60
61 pub fn patchify_fragment(&self, input: &TensorDesc, patch: usize) -> Result<ModelFragment> {
63 if input.dtype() != DType::F32
64 || input.layout() != Layout::Nchw
65 || !input.is_contiguous()
66 || input.is_empty()
67 {
68 return Err(Error::Message(
69 "patchify requires nonempty contiguous NCHW f32".into(),
70 ));
71 }
72 let [batch, channels, height, width]: [usize; 4] = input
73 .shape()
74 .try_into()
75 .map_err(|_| Error::Message("patchify requires four axes".into()))?;
76 if patch == 0
77 || !height.is_multiple_of(patch)
78 || !width.is_multiple_of(patch)
79 || input.elements() > i32::MAX as usize
80 {
81 return Err(Error::Message("invalid patch or image size".into()));
82 }
83 let count = input.elements();
84 let cells = (height / patch) * (width / patch);
85 let patch_elements = channels * patch * patch;
86 let output = TensorDesc::new(DType::F32, vec![batch, cells, patch_elements])?;
87 let source = format!(
88 r#"
89amdgpu.target<gfx11-generic> @image_target {{subgroup_size = 32}}
90kernel.def target(@image_target) export("patchify") @patchify(%unused: index) {{
91 %one = index.constant 1 : index
92 %threads = index.constant 256 : index
93 %grid = index.constant {grid} : index
94 kernel.launch.config workgroups(%grid, %one, %one) workgroup_size(%threads, %one, %one) : index
95}} launch(%unused: index, %input: buffer, %output: buffer) {{
96 %zero = index.constant 0 : offset
97 %threads = index.constant 256 : index
98 %lane = kernel.workitem.id<x> : index
99 %group = kernel.workgroup.id<x> : index
100 %base = index.mul %group, %threads : index
101 %raw = index.add %base, %lane : index
102 %count = index.constant {count} : index
103 %p = index.constant {patch} : index
104 %pp = index.constant {pp} : index
105 %pe = index.constant {patch_elements} : index
106 %ch = index.constant {channels} : index
107 %pxs = index.constant {pxs} : index
108 %cells = index.constant {cells} : index
109 %h = index.constant {height} : index
110 %w = index.constant {width} : index
111 %inputg = buffer.assume.memory_space<global> %input : buffer
112 %outputg = buffer.assume.memory_space<global> %output : buffer
113 %src = buffer.view %inputg[%zero] : buffer -> view<{count}xf32>
114 %dst = buffer.view %outputg[%zero] : buffer -> view<{count}xf32>
115 %valid = index.cmp ult, %raw, %count : index
116 scf.if %valid {{
117 %i = index.assume %raw [range(%raw, 0, {count})] : index
118 %dx = index.rem %i, %p : index
119 %qdy = index.div %i, %p : index
120 %dy = index.rem %qdy, %p : index
121 %qc = index.div %i, %pp : index
122 %c = index.rem %qc, %ch : index
123 %cell = index.div %i, %pe : index
124 %image = index.div %cell, %cells : index
125 %incell = index.rem %cell, %cells : index
126 %px = index.rem %incell, %pxs : index
127 %py = index.div %incell, %pxs : index
128 %x0 = index.mul %px, %p : index
129 %x = index.add %x0, %dx : index
130 %y0 = index.mul %py, %p : index
131 %y = index.add %y0, %dy : index
132 %ic0 = index.mul %image, %ch : index
133 %ic = index.add %ic0, %c : index
134 %iy0 = index.mul %ic, %h : index
135 %iy = index.add %iy0, %y : index
136 %ix0 = index.mul %iy, %w : index
137 %ix = index.add %ix0, %x : index
138 %j = index.assume %ix [range(%ix, 0, {count})] : index
139 %value = view.load %src[%j] : view<{count}xf32> -> f32
140 view.store %value, %dst[%i] : f32, view<{count}xf32>
141 }}
142 kernel.return
143}}
144"#,
145 grid = count.div_ceil(256),
146 pp = patch * patch,
147 pxs = width / patch
148 );
149 let mut model = ModelSession::in_context(&self.context)?;
150 let src = model.allocate(input.bytes())?;
151 let dst = model.allocate(output.bytes())?;
152 let kernel = unsafe { model.compile(&[(&source, Specialization::new("patchify"))])? }[0];
154 let definition = model.freeze(&self.context)?;
155 unsafe {
156 definition.fragment(
157 &[Command::Dispatch(Dispatch::indices(
158 kernel,
159 [0],
160 [count.div_ceil(256) as u32, 1, 1],
161 vec![src.read(), dst.write()],
162 ))],
163 &[(src, input.clone())],
164 &[(dst, output)],
165 )
166 }
167 }
168
169 pub fn patchify(&self, input: &DeviceTensor, patch: usize) -> Result<Inference> {
171 self.context.validate(input)?;
172 self.prepare_patchify(input.desc(), patch)?
173 .submit(std::slice::from_ref(input))
174 }
175}