1use super::adapter_manager::{LoRAAdapter, LoRAError};
17
18#[repr(C)]
21#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
22pub struct LoraGpuParams {
23 pub n_in: u32,
24 pub n_out: u32,
25 pub rank: u32,
26 pub scaling: f32,
27}
28
29pub struct LoRAGpuApplicator {
35 device: wgpu::Device,
36 queue: wgpu::Queue,
37 pipeline: wgpu::ComputePipeline,
38 bgl: wgpu::BindGroupLayout,
39}
40
41impl LoRAGpuApplicator {
42 pub fn new() -> Result<Self, LoRAError> {
47 let rt = Box::leak(Box::new(
48 tokio::runtime::Runtime::new()
49 .map_err(|e| LoRAError::Io(format!("tokio runtime: {e}")))?,
50 ));
51 rt.block_on(Self::new_async())
52 }
53
54 async fn new_async() -> Result<Self, LoRAError> {
55 let instance = wgpu::Instance::default();
56 let adapter = instance
57 .request_adapter(&wgpu::RequestAdapterOptions {
58 power_preference: wgpu::PowerPreference::HighPerformance,
59 ..Default::default()
60 })
61 .await
62 .map_err(|e| LoRAError::Io(format!("no wgpu adapter: {e}")))?;
63
64 let (device, queue) = adapter
67 .request_device(&wgpu::DeviceDescriptor::default())
68 .await
69 .map_err(|e| LoRAError::Io(format!("wgpu device: {e}")))?;
70
71 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
72 label: Some("lora_apply"),
73 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/lora_apply.wgsl").into()),
74 });
75
76 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
77 label: Some("lora-bgl"),
78 entries: &[
79 storage_rw_entry(0), storage_r_entry(1), storage_r_entry(2), storage_r_entry(3), uniform_entry(4), ],
85 });
86
87 let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
88 label: Some("lora-pl"),
89 bind_group_layouts: &[Some(&bgl)],
90 immediate_size: 0,
91 });
92
93 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
94 label: Some("lora-pipeline"),
95 layout: Some(&pl),
96 module: &shader,
97 entry_point: Some("apply_lora"),
98 compilation_options: Default::default(),
99 cache: None,
100 });
101
102 Ok(Self {
103 device,
104 queue,
105 pipeline,
106 bgl,
107 })
108 }
109
110 pub fn apply(
117 &self,
118 adapter: &LoRAAdapter,
119 input: &[f32],
120 output: &mut [f32],
121 ) -> Result<(), LoRAError> {
122 let n_in = adapter.meta.n_in;
123 let n_out = adapter.meta.n_out;
124 let rank = adapter.meta.rank as usize;
125 let scaling = adapter.meta.scaling();
126
127 if input.len() != n_in || output.len() != n_out {
128 return Err(LoRAError::InferenceDimMismatch {
129 input_len: input.len(),
130 lora_n_in: n_in,
131 });
132 }
133
134 let params = LoraGpuParams {
135 n_in: n_in as u32,
136 n_out: n_out as u32,
137 rank: rank as u32,
138 scaling,
139 };
140
141 let dev = &self.device;
142
143 let out_buf = buf_rw(dev, bytemuck::cast_slice(output));
145 let in_buf = buf_r(dev, bytemuck::cast_slice(input));
146 let a_buf = buf_r(dev, bytemuck::cast_slice(&adapter.lora_a.data));
147 let b_buf = buf_r(dev, bytemuck::cast_slice(&adapter.lora_b.data));
148 let params_buf = buf_uniform(dev, bytemuck::bytes_of(¶ms));
149
150 let out_staging = dev.create_buffer(&wgpu::BufferDescriptor {
151 label: Some("lora-out-staging"),
152 size: (n_out * 4) as u64,
153 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
154 mapped_at_creation: false,
155 });
156
157 let bg = dev.create_bind_group(&wgpu::BindGroupDescriptor {
159 label: Some("lora-bg"),
160 layout: &self.bgl,
161 entries: &[
162 wgpu::BindGroupEntry {
163 binding: 0,
164 resource: out_buf.as_entire_binding(),
165 },
166 wgpu::BindGroupEntry {
167 binding: 1,
168 resource: in_buf.as_entire_binding(),
169 },
170 wgpu::BindGroupEntry {
171 binding: 2,
172 resource: a_buf.as_entire_binding(),
173 },
174 wgpu::BindGroupEntry {
175 binding: 3,
176 resource: b_buf.as_entire_binding(),
177 },
178 wgpu::BindGroupEntry {
179 binding: 4,
180 resource: params_buf.as_entire_binding(),
181 },
182 ],
183 });
184
185 let mut enc = dev.create_command_encoder(&wgpu::CommandEncoderDescriptor {
187 label: Some("lora-enc"),
188 });
189
190 {
191 let mut cpass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
192 label: Some("lora-pass"),
193 timestamp_writes: None,
194 });
195 cpass.set_pipeline(&self.pipeline);
196 cpass.set_bind_group(0, &bg, &[]);
197 cpass.dispatch_workgroups((n_out as u32 + 63) / 64, 1, 1);
198 }
199
200 enc.copy_buffer_to_buffer(&out_buf, 0, &out_staging, 0, (n_out * 4) as u64);
201 self.queue.submit(Some(enc.finish()));
202
203 let slice = out_staging.slice(..);
205 let (tx, rx) = std::sync::mpsc::channel();
206 slice.map_async(wgpu::MapMode::Read, move |r| {
207 let _ = tx.send(r);
208 });
209 let _ = dev.poll(wgpu::PollType::wait_indefinitely());
210 rx.recv()
211 .map_err(|_| LoRAError::Io("GPU readback channel closed".into()))?
212 .map_err(|e| LoRAError::Io(format!("map_async: {e:?}")))?;
213
214 let mapped = slice
215 .get_mapped_range()
216 .expect("wgpu buffer map_range failed");
217 let result: &[f32] = bytemuck::cast_slice(&mapped);
218 output.copy_from_slice(result);
219 Ok(())
220 }
221}
222
223fn buf_rw(dev: &wgpu::Device, data: &[u8]) -> wgpu::Buffer {
226 use wgpu::util::DeviceExt;
227 dev.create_buffer_init(&wgpu::util::BufferInitDescriptor {
228 label: Some("lora-rw"),
229 contents: data,
230 usage: wgpu::BufferUsages::STORAGE
231 | wgpu::BufferUsages::COPY_SRC
232 | wgpu::BufferUsages::COPY_DST,
233 })
234}
235
236fn buf_r(dev: &wgpu::Device, data: &[u8]) -> wgpu::Buffer {
237 use wgpu::util::DeviceExt;
238 dev.create_buffer_init(&wgpu::util::BufferInitDescriptor {
239 label: Some("lora-r"),
240 contents: data,
241 usage: wgpu::BufferUsages::STORAGE,
242 })
243}
244
245fn buf_uniform(dev: &wgpu::Device, data: &[u8]) -> wgpu::Buffer {
246 use wgpu::util::DeviceExt;
247 dev.create_buffer_init(&wgpu::util::BufferInitDescriptor {
248 label: Some("lora-uniform"),
249 contents: data,
250 usage: wgpu::BufferUsages::UNIFORM,
251 })
252}
253
254fn storage_rw_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
255 wgpu::BindGroupLayoutEntry {
256 binding,
257 visibility: wgpu::ShaderStages::COMPUTE,
258 ty: wgpu::BindingType::Buffer {
259 ty: wgpu::BufferBindingType::Storage { read_only: false },
260 has_dynamic_offset: false,
261 min_binding_size: None,
262 },
263 count: None,
264 }
265}
266
267fn storage_r_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
268 wgpu::BindGroupLayoutEntry {
269 binding,
270 visibility: wgpu::ShaderStages::COMPUTE,
271 ty: wgpu::BindingType::Buffer {
272 ty: wgpu::BufferBindingType::Storage { read_only: true },
273 has_dynamic_offset: false,
274 min_binding_size: None,
275 },
276 count: None,
277 }
278}
279
280fn uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
281 wgpu::BindGroupLayoutEntry {
282 binding,
283 visibility: wgpu::ShaderStages::COMPUTE,
284 ty: wgpu::BindingType::Buffer {
285 ty: wgpu::BufferBindingType::Uniform,
286 has_dynamic_offset: false,
287 min_binding_size: None,
288 },
289 count: None,
290 }
291}