use bytemuck::{Pod, Zeroable}; use half::f16; use std::borrow::Cow; use wgpu::util::DeviceExt; #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] struct Params { m: u32, n: u32, k: u32, pad: u32 } const WGSL: &str = r#" enable f16; struct Params { m: u32, n: u32, k: u32, pad: u32 }; @group(0) @binding(0) var a: array; @group(0) @binding(1) var b: array; @group(0) @binding(2) var c: array; @group(0) @binding(3) var p: Params; var As: array; var Bs: array; @compute @workgroup_size(16, 16) fn main(@builtin(workgroup_id) wid: vec3, @builtin(local_invocation_id) lid: vec3) { let tx = lid.x; let ty = lid.y; let row0 = wid.x * 16u; let col0 = wid.y * 16u; let row = row0 + ty; let col = col0 + tx; var acc = 0.0; var k0 = 0u; loop { if (k0 >= p.k) { break; } if (row < p.m && (k0 + tx) < p.k) { As[ty * 16u + tx] = a[row * p.k + k0 + tx]; } else { As[ty * 16u + tx] = f16(0.0); } if ((col0 + ty) < p.n && (k0 + tx) < p.k) { Bs[ty * 16u + tx] = b[(col0 + ty) * p.k + k0 + tx]; } else { Bs[ty * 16u + tx] = f16(0.0); } workgroupBarrier(); var kk = 0u; loop { if (kk >= 16u) { break; } acc = acc + f32(As[ty * 16u + kk]) * f32(Bs[tx * 16u + kk]); kk = kk + 1u; } workgroupBarrier(); k0 = k0 + 16u; } if (row < p.m && col < p.n) { c[row * p.n + col] = f16(acc); } } "#; fn gelu(x: f32) -> f32 { 0.5 * x * (1.0 + (0.79788456 * (x + 0.044715 * x * x * x)).tanh()) } fn q(v: f32) -> f32 { f16::from_f32(v).to_f32() } fn cpu_ffn(x: &[f32], w1: &[f32], w2: &[f32], m: usize, d: usize, f: usize) -> Vec { let mut up = vec![0.0f32; m * f]; for i in 0..m { for j in 0..f { let mut acc = 0.0f32; for k in 0..d { acc += q(x[i * d + k]) * q(w1[j * d + k]); } up[i * f + j] = q(gelu(q(acc))); }} let mut y = vec![0.0f32; m * d]; for i in 0..m { for j in 0..d { let mut acc = 0.0f32; for k in 0..f { acc += q(up[i * f + k]) * q(w2[j * f + k]); } y[i * d + j] = q(acc); }} y } fn main() { let (m, d, f) = (1560usize, 1536usize, 8960usize); let raw = std::fs::read("D:/3DWord/itseez3d/ai-lab/lingbot-world/port/wgpu-rms-layer-h2.bin").expect("h2.bin"); let x16: Vec = bytemuck::cast_slice::(&raw)[..m * d].to_vec(); let x: Vec = x16.iter().map(|v| v.to_f32()).collect(); let mut w1 = vec![0.0f32; f * d]; let mut w2 = vec![0.0f32; d * f]; for i in 0..w1.len() { w1[i] = ((i * 17 % 97) as f32 - 48.0) / (d as f32 * 8.0); } for i in 0..w2.len() { w2[i] = ((i * 23 % 89) as f32 - 44.0) / (f as f32 * 8.0); } println!("cpu reference..."); let raw2 = std::fs::read("D:/3DWord/itseez3d/ai-lab/lingbot-world/port/wgpu-qkv-rope-out.bin").expect("out.bin"); let x2: Vec = bytemuck::cast_slice::(&raw2)[..m * d].to_vec(); let eraw = std::fs::read("D:/3DWord/itseez3d/ai-lab/lingbot-world/port/wgpu-time-e.bin").expect("time e"); let efull: &[f32] = bytemuck::cast_slice(&eraw); let e5: Vec = efull[5 * d..6 * d].to_vec(); let mut expect = cpu_ffn(&x[..2 * d], &w1, &w2, 2, d, f); for i in 0..2 { for j in 0..d { expect[i * d + j] = expect[i * d + j] * e5[j] + x2[i * d + j]; } } let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::VULKAN, ..Default::default() }); let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: None, force_fallback_adapter: false, })).expect("no Vulkan adapter"); let info = adapter.get_info(); println!("adapter: {} | backend: {:?}", info.name, info.backend); let mut limits = wgpu::Limits::downlevel_defaults(); limits.max_storage_buffer_binding_size = 256 << 20; let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { label: Some("ffn"), required_features: wgpu::Features::SHADER_F16, required_limits: limits, ..Default::default() })).expect("device"); let mk = |data: &[f16]| device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(data), usage: wgpu::BufferUsages::STORAGE, }); let x16: Vec = x.iter().map(|v| f16::from_f32(*v)).collect(); let w1_16: Vec = w1.iter().map(|v| f16::from_f32(*v)).collect(); let w2_16: Vec = w2.iter().map(|v| f16::from_f32(*v)).collect(); let x_buf = mk(&x16); let w1_buf = mk(&w1_16); let w2_buf = mk(&w2_16); let ffn_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: (m * f * 2) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false }); let act_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::cast_slice(&vec![f16::from_f32(0.0); m * f]), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, }); let y_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: (m * d * 2) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false }); let up_p = Params { m: m as u32, n: f as u32, k: d as u32, pad: 0 }; let dn_p = Params { m: m as u32, n: d as u32, k: f as u32, pad: 0 }; let up_pb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::bytes_of(&up_p), usage: wgpu::BufferUsages::UNIFORM }); let dn_pb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, contents: bytemuck::bytes_of(&dn_p), usage: wgpu::BufferUsages::UNIFORM }); let up_rb = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: (m * f * 2) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false }); let y_rb = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: (m * d * 2) as u64, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false }); let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(WGSL)) }); let pipe = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { label: None, layout: None, module: &module, entry_point: Some("main"), compilation_options: Default::default(), cache: None }); let bind = |x: &wgpu::Buffer, w: &wgpu::Buffer, y: &wgpu::Buffer, pb: &wgpu::Buffer| device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &pipe.get_bind_group_layout(0), entries: &[ wgpu::BindGroupEntry { binding: 0, resource: x.as_entire_binding() }, wgpu::BindGroupEntry { binding: 1, resource: w.as_entire_binding() }, wgpu::BindGroupEntry { binding: 2, resource: y.as_entire_binding() }, wgpu::BindGroupEntry { binding: 3, resource: pb.as_entire_binding() }, ]}); let up_bg = bind(&x_buf, &w1_buf, &ffn_buf, &up_pb); let dn_bg = bind(&act_buf, &w2_buf, &y_buf, &dn_pb); let mut enc = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut p = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None }); p.set_pipeline(&pipe); p.set_bind_group(0, &up_bg, &[]); p.dispatch_workgroups(((m + 15) / 16) as u32, ((f + 15) / 16) as u32, 1); } enc.copy_buffer_to_buffer(&ffn_buf, 0, &up_rb, 0, (m * f * 2) as u64); queue.submit(Some(enc.finish())); let su = up_rb.slice(..); su.map_async(wgpu::MapMode::Read, |r| r.expect("map")); device.poll(wgpu::PollType::wait_indefinitely()).expect("poll"); let du = su.get_mapped_range(); let up: &[f16] = bytemuck::cast_slice(&du); let mut act = vec![f16::from_f32(0.0); m * f]; for i in 0..m * f { act[i] = f16::from_f32(gelu(up[i].to_f32())); } drop(du); queue.write_buffer(&act_buf, 0, bytemuck::cast_slice(&act)); let mut enc = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { let mut p = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, timestamp_writes: None }); p.set_pipeline(&pipe); p.set_bind_group(0, &dn_bg, &[]); p.dispatch_workgroups(((m + 15) / 16) as u32, ((d + 15) / 16) as u32, 1); } enc.copy_buffer_to_buffer(&y_buf, 0, &y_rb, 0, (m * d * 2) as u64); let t0 = std::time::Instant::now(); queue.submit(Some(enc.finish())); let sy = y_rb.slice(..); sy.map_async(wgpu::MapMode::Read, |r| r.expect("map")); device.poll(wgpu::PollType::wait_indefinitely()).expect("poll"); let dy = sy.get_mapped_range(); let y: &[f16] = bytemuck::cast_slice(&dy); let mut max_err = 0.0f32; for i in 0..2 * d { let got = y[i].to_f32() * e5[i % d] + x2[i]; max_err = max_err.max((got - expect[i]).abs()); } let mut y16 = vec![f16::from_f32(0.0); m * d]; for i in 0..m * d { y16[i] = f16::from_f32(y[i].to_f32() * e5[i % d] + x2[i]); } std::fs::write("D:/3DWord/itseez3d/ai-lab/lingbot-world/port/wgpu-layer-y.bin", bytemuck::cast_slice(&y16)).expect("write y"); println!("ffn split M={m} D={d} F={f} down_ms={:.3} max_abs_err={max_err:.3e}", t0.elapsed().as_secs_f64() * 1000.0); assert!(max_err < 5e-2, "ffn mismatch"); println!("VULKAN SPLIT FFN OK"); }