13#include "common/config.h"
28constexpr int kAutotuneIters = 5;
31const char *kReduceSource = R
"(
32@group(0) @binding(0) var<storage, read_write> input: array<f32>;
33@group(0) @binding(1) var<storage, read_write> partial: array<f32>;
34struct Params { data: array<vec4<f32>, 8>, }
35@group(0) @binding(8) var<uniform> pc: Params;
36var<workgroup> scratch: array<f32, 256>;
37@compute @workgroup_size(256)
38fn main(@builtin(local_invocation_id) lid: vec3<u32>,
39 @builtin(global_invocation_id) gid: vec3<u32>,
40 @builtin(workgroup_id) wid: vec3<u32>) {
41 let size = u32(pc.data[0].x + 0.5);
42 let op = u32(pc.data[0].y + 0.5);
43 scratch[lid.x] = select(select(-3.402823e38f, 3.402823e38f, op == 1u), 0.0f, op == 0u);
44 if (gid.x < size) { scratch[lid.x] = input[gid.x]; }
46 for (var step = 128u; step > 0u; step >>= 1u) {
48 if (op == 0u) { scratch[lid.x] += scratch[lid.x + step]; }
49 else if (op == 1u) { scratch[lid.x] = min(scratch[lid.x], scratch[lid.x + step]); }
50 else { scratch[lid.x] = max(scratch[lid.x], scratch[lid.x + step]); }
54 if (lid.x == 0u) { partial[wid.x] = scratch[0]; }
58const char *kReduceSource = R
"(#version 450
59layout(local_size_x = 256) in;
60layout(set = 0, binding = 0) readonly buffer In { float a[]; };
61layout(set = 0, binding = 1) writeonly buffer Out { float partial[]; };
62layout(push_constant) uniform PC { float data[32]; } pc;
63shared float sdata[256];
65 uint tid = gl_LocalInvocationID.x;
66 uint gid = gl_GlobalInvocationID.x;
67 uint size = uint(pc.data[0] + 0.5);
68 int op = int(pc.data[1] + 0.5);
69 float ident = (op == 0) ? 0.0 : (op == 1 ? 3.402823e38 : -3.402823e38);
70 sdata[tid] = (gid < size) ? a[gid] : ident;
72 for (uint s = 128u; s > 0u; s >>= 1u) {
74 if (op == 0) sdata[tid] += sdata[tid + s];
75 else if (op == 1) sdata[tid] = min(sdata[tid], sdata[tid + s]);
76 else sdata[tid] = max(sdata[tid], sdata[tid + s]);
80 if (tid == 0u) partial[gl_WorkGroupID.x] = sdata[0];
88 gpgpu::ComputeShader *
reduce =
nullptr;
92ReduceKernels *getReduceKernels() {
93 static ReduceKernels *kernels =
nullptr;
94 static bool failed =
false;
95 if (kernels)
return kernels;
96 if (
failed)
return nullptr;
98 auto *
gp = gpgpu::Gpgpu::create();
99 if (!
gp || !
gp->isAvailable()) {
103 auto *k =
new ReduceKernels();
105 k->reduce =
gp->newShader(kReduceSource);
116Result<KernelSpec> backendKernel(
const Graph &
graph,
const FusedGroup &
group,
118#ifdef EVENGINE_WEBGPU
132void bindKernel(gpgpu::ComputeShader *
shader,
const KernelSpec &spec,
133 const std::vector<gpgpu::GpuBuffer *> &
inputs, gpgpu::GpuBuffer *
output) {
134 for (
int i = 0; i < spec.inputCount; ++i)
136 spec.inputRepresentatives.empty() || spec.inputRepresentatives[
static_cast<size_t>(i)] == i
137 ?
inputs[
static_cast<size_t>(i)]
142double timeDispatch(gpgpu::Gpgpu *
gp, gpgpu::ComputeShader *
shader,
int gx,
int gy) {
143 const auto t0 = std::chrono::steady_clock::now();
144 for (
int i = 0; i < kAutotuneIters; ++i)
gp->dispatch(
shader, gx, gy, 1);
145 const auto t1 = std::chrono::steady_clock::now();
146 return std::chrono::duration<double, std::milli>(t1 - t0).count() / kAutotuneIters;
154 std::unique_ptr<gpgpu::ComputeShader>
pass1;
155 std::unique_ptr<gpgpu::ComputeShader>
pass2;
157 std::vector<gpgpu::GpuBuffer *>
stats;
190 if (
g.spec.twoPass) {
191 auto *p1 =
g.pass1.get();
192 for (
int i = 0; i <
g.spec.inputsReadPass1; ++i)
194 i,
g.spec.inputRepresentatives.empty() ||
g.spec.inputRepresentatives[
static_cast<size_t>(i)] == i
195 ?
g.inputs[
static_cast<size_t>(i)]
197 for (
int s = 0;
s <
g.spec.statsCount; ++
s)
198 p1->bindBuffer(
g.spec.inputsReadPass1 +
s,
g.stats[
static_cast<size_t>(
s)]);
200 auto *p2 =
g.pass2.get();
201 for (
int i = 0; i <
g.spec.inputCount; ++i)
203 i,
g.spec.inputRepresentatives.empty() ||
g.spec.inputRepresentatives[
static_cast<size_t>(i)] == i
204 ?
g.inputs[
static_cast<size_t>(i)]
206 if (
g.spec.scalesBinding >= 0 &&
g.qScales)
207 p2->bindBuffer(
g.spec.scalesBinding,
g.qScales);
208 const int outBinding =
g.spec.outputBinding >= 0 ?
g.spec.outputBinding
210 if (
g.spec.twoPass) {
211 for (
int s = 0;
s <
g.spec.statsCount; ++
s)
212 p2->bindBuffer(
g.spec.inputCount +
s,
g.stats[
static_cast<size_t>(
s)]);
213 p2->bindBuffer(
g.spec.inputCount +
g.spec.statsCount,
g.output);
215 p2->bindBuffer(outBinding,
g.output);
229GpuProgram::GpuProgram() : impl_(new Impl()) {}
233 auto *
gp = gpgpu::Gpgpu::create();
234 if (!
gp || !
gp->isAvailable())
return nullptr;
235 if (outputNode < 0 || outputNode >=
graph.nodeCount())
return nullptr;
238 prog->impl_->gpgpu =
gp;
239 auto &
impl = *prog->impl_;
247 impl.sequence.reset(
gp->newSequence());
250 impl.placeholderBuffers.clear();
251 impl.placeholderSizes.clear();
252 for (
int id = 0;
id <
graph.nodeCount(); ++
id) {
253 const auto &nd =
graph.node(
id);
254 if (opt.
nodeSlot[
static_cast<size_t>(
id)] < 0)
continue;
255 auto *buf =
impl.slotBuffer[
static_cast<size_t>(opt.
nodeSlot[
static_cast<size_t>(
id)])];
257 const int slot = nd.placeholderSlot;
258 if (slot < 0)
throw eve::Exception(
"GpuProgram: bad placeholder slot");
259 if (
int(
impl.placeholderBuffers.size()) <= slot) {
260 impl.placeholderBuffers.resize(
size_t(slot) + 1,
nullptr);
261 impl.placeholderSizes.resize(
size_t(slot) + 1, 0);
263 impl.placeholderBuffers[
static_cast<size_t>(slot)] = buf;
264 impl.placeholderSizes[
static_cast<size_t>(slot)] = nd.size;
266 if (!nd.constBytes.empty()) {
268 if (nd.constBytes.size() % 4 != 0) {
269 auto bytes = nd.constBytes;
270 bytes.resize((
bytes.size() + 3) & ~
size_t(3), 0);
273 buf->uploadBytes(nd.constBytes.data(), nd.constBytes.size());
275 if (!nd.constScales.empty()) {
276 auto *sb =
impl.alloc(
int(nd.constScales.size()) *
int(
sizeof(
float)));
277 sb->uploadBytes(nd.constScales.data(),
278 sizeof(
float) * nd.constScales.size());
279 impl.qScalesByNode[
id] = sb;
282 buf->uploadBytes(nd.constData.data(),
sizeof(
float) *
size_t(nd.size));
289 const auto &grp = opt.
groups[
static_cast<size_t>(gi)];
293 for (
int input : grp.inputs) {
295 if (slot < 0)
throw eve::Exception(
"GpuProgram: input without slot");
296 rt.
inputs.push_back(
impl.slotBuffer[
static_cast<size_t>(slot)]);
299 auto it =
impl.qScalesByNode.find(
input);
300 if (it !=
impl.qScalesByNode.end()) rt.
qScales = it->second;
303 const int outSlot = opt.
nodeSlot[
static_cast<size_t>(grp.outputNode)];
304 if (outSlot < 0)
throw eve::Exception(
"GpuProgram: output without slot");
305 rt.
output =
impl.slotBuffer[
static_cast<size_t>(outSlot)];
310 auto naiveResult = backendKernel(
graph, grp);
311 if (!naiveResult)
throw eve::Exception(
"GpuProgram: matmul codegen failed");
312 KernelSpec naive = std::move(naiveResult).value();
315 std::unique_ptr<gpgpu::ComputeShader> naiveShader, tiledShader;
317 KernelSpec tiled = std::move(tiledResult).value();
318 naiveShader.reset(
gp->newShader(naive.
pass2));
319 tiledShader.reset(
gp->newShader(tiled.
pass2));
323 bindKernel(naiveShader.get(), naive, rt.
inputs, rt.
output);
324 const double tNaive =
325 timeDispatch(
gp, naiveShader.get(), naive.
groupsX2,
327 bindKernel(tiledShader.get(), tiled, rt.
inputs, rt.
output);
328 const double tTiled =
329 timeDispatch(
gp, tiledShader.get(), tiled.
groupsX2,
331 if (tTiled < tNaive) {
333 rt.
pass2 = std::move(tiledShader);
336 rt.
pass2 = std::move(naiveShader);
340 rt.
pass2 = std::move(naiveShader);
351 auto result = backendKernel(
graph, grp);
352 if (!result)
throw eve::Exception(
"GpuProgram: kernel codegen failed");
353 spec = std::move(result).value();
362 impl.groups.push_back(std::move(rt));
366 const int outSlot = opt.
nodeSlot[
static_cast<size_t>(outputNode)];
367 if (outSlot < 0)
throw eve::Exception(
"GpuProgram: final output without slot");
368 impl.outputBuffer =
impl.slotBuffer[
static_cast<size_t>(outSlot)];
369 impl.outputSize =
graph.node(outputNode).size;
370 impl.outputStaging.reset(
371 gp->newBuffer(
impl.outputSize *
int(
sizeof(
float)),
"staging"));
373 }
catch (
const std::exception &e) {
374 fprintf(stderr,
"[tensor] GpuProgram::tryBuild failed: %s\n", e.what());
386 for (
size_t i = 0; i < feeds.size(); ++i) {
392 const uint64_t outBytes =
sizeof(float) *
size_t(impl_->
outputSize);
396 std::vector<float> out(
static_cast<size_t>(impl_->
outputSize));
402 if (!data ||
size <= 0)
return false;
403 ReduceKernels *kernels = getReduceKernels();
404 if (!kernels)
return false;
406 std::unique_ptr<gpgpu::GpuBuffer> in(
407 kernels->gpgpu->newBuffer(
size *
int(
sizeof(
float)),
"storage"));
408 in->uploadBytes(data,
sizeof(
float) *
size_t(
size));
411 std::unique_ptr<gpgpu::GpuBuffer> partial(
412 kernels->gpgpu->newBuffer(
groups *
int(
sizeof(
float)),
"storage"));
414 kernels->reduce->bindBuffer(0, in.get());
415 kernels->reduce->bindBuffer(1, partial.get());
416 kernels->reduce->setFloat(0,
float(
size));
417 kernels->reduce->setFloat(1,
float(op));
418 kernels->gpgpu->dispatch(kernels->reduce,
groups);
420 std::vector<float>
parts(
static_cast<size_t>(
groups));
421 partial->downloadBytes(
parts.data(),
sizeof(
float) *
size_t(
groups));
423 float acc = op == 0 ? 0.f
424 : (op == 1 ? std::numeric_limits<float>::max()
425 : -std::numeric_limits<float>::max());
427 if (op == 0) acc +=
v;
428 else if (op == 1) acc = std::min(acc,
v);
429 else acc = std::max(acc,
v);
std::vector< eve::artifact::PartView > parts
building::EdgeCurveGroup group
std::unique_ptr< gpgpu::GpuBuffer > buffer
const RuntimeTensor * borrowed
std::map< std::string, std::vector< std::string > > graph
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
EVENGINE_API_FOUNDATION public API.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
GPGPU module — compute shaders + storage buffers via the active Graphics backend. Uses the graphics q...
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Backend-agnostic GPU buffer for compute (storage) or CPU staging transfers. Squirrel-owned; derived c...
EVENGINE_API_WORLD public API.
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
Record upload.
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Record download.
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Record dispatch.
void begin()
Begins begin.
GPU execution of a compiled tensor Graph via generated compute shaders.
std::vector< float > run(const std::vector< const float * > &feeds) const
feeds[slot] must point to placeholderSize(slot) floats. Returns the output buffer.
static GpuProgram * tryBuild(const Graph &graph, const OptimizedGraph &opt, int outputNode)
Try build.
~GpuProgram()
Gpu program.
EVENGINE_API_DOMAINS public API.
Result< T > failed(Status status, eve::DiagnosticCode code, RuleId rule, std::string message)
Construct a failed editing result with explicit status and diagnostic categories.
int groupsFor(int count)
Groups for.
KernelVariant
Internal choice of generated matrix multiplication implementation.
bool generateKernel(const Graph &graph, const FusedGroup &group, KernelSpec &out)
Generate kernel.
bool generateMatMulVariant(const Graph &graph, const FusedGroup &group, bool tiled, KernelSpec &out)
Generate mat mul variant.
Result< KernelSpec > generateWgslKernel(const Graph &graph, const FusedGroup &group, KernelVariant variant)
Lower an optimizer-produced group directly to owning WGSL source and dispatch metadata.
bool gpuReduce(const float *data, int size, int op, float &outResult)
GPU-accelerated reduction for large eager tensors. op: 0 = sum, 1 = min, 2 = max. Returns false (call...
gpgpu::GpuBuffer * output
gpgpu::GpuBuffer * qScales
std::unique_ptr< gpgpu::ComputeShader > pass1
std::unique_ptr< gpgpu::ComputeShader > pass2
std::vector< gpgpu::GpuBuffer * > stats
std::vector< gpgpu::GpuBuffer * > inputs
std::vector< GroupRuntime > groups
std::vector< int > placeholderSizes
std::unique_ptr< gpgpu::Sequence > sequence
std::vector< gpgpu::GpuBuffer * > placeholderBuffers
gpgpu::GpuBuffer * alloc(int byteSize)
gpgpu::GpuBuffer * outputBuffer
void bindGroup(const GroupRuntime &g) const
std::vector< gpgpu::GpuBuffer * > slotBuffer
void recordGroup(const GroupRuntime &g, gpgpu::Sequence *seq) const
std::unique_ptr< gpgpu::GpuBuffer > outputStaging
std::map< int, gpgpu::GpuBuffer * > qScalesByNode
std::vector< std::unique_ptr< gpgpu::GpuBuffer > > ownedBuffers
std::vector< float > constScales
std::vector< uint8_t > constBytes
OptimizedGraph public API.
std::vector< int > slotSize
std::vector< int > nodeSlot
std::vector< int > groupOrder
std::vector< FusedGroup > groups
gpgpu::ComputeShader * reduce