25constexpr int kLocalSize = 256;
26constexpr int kAutotuneIters = 5;
28const char *kReduceGlsl = R
"(#version 450
29layout(local_size_x = 256) in;
30layout(set = 0, binding = 0) readonly buffer In { float a[]; };
31layout(set = 0, binding = 1) writeonly buffer Out { float partial[]; };
32layout(push_constant) uniform PC { float data[32]; } pc;
33shared float sdata[256];
35 uint tid = gl_LocalInvocationID.x;
36 uint gid = gl_GlobalInvocationID.x;
37 uint size = uint(pc.data[0] + 0.5);
38 int op = int(pc.data[1] + 0.5);
39 float ident = (op == 0) ? 0.0 : (op == 1 ? 3.402823e38 : -3.402823e38);
40 sdata[tid] = (gid < size) ? a[gid] : ident;
42 for (uint s = 128u; s > 0u; s >>= 1u) {
44 if (op == 0) sdata[tid] += sdata[tid + s];
45 else if (op == 1) sdata[tid] = min(sdata[tid], sdata[tid + s]);
46 else sdata[tid] = max(sdata[tid], sdata[tid + s]);
50 if (tid == 0u) partial[gl_WorkGroupID.x] = sdata[0];
56 gpgpu::ComputeShader *
reduce =
nullptr;
60ReduceKernels *getReduceKernels() {
61 static ReduceKernels *kernels =
nullptr;
62 static bool failed =
false;
63 if (kernels)
return kernels;
64 if (failed)
return nullptr;
66 auto *gp = gpgpu::Gpgpu::create();
67 if (!gp || !gp->isAvailable()) {
71 auto *k =
new ReduceKernels();
73 k->reduce = gp->newShader(kReduceGlsl);
82int groupsFor(
int count) {
return (count + kLocalSize - 1) / kLocalSize; }
85void bindKernel(gpgpu::ComputeShader *
shader,
const KernelSpec &spec,
86 const std::vector<gpgpu::GpuBuffer *> &inputs, gpgpu::GpuBuffer *output) {
87 for (
int i = 0; i < spec.inputCount; ++i)
88 shader->bindBuffer(i, inputs[
static_cast<size_t>(i)]);
89 shader->bindBuffer(spec.inputCount, output);
92double timeDispatch(gpgpu::Gpgpu *gp, gpgpu::ComputeShader *
shader,
int gx,
int gy) {
93 const auto t0 = std::chrono::steady_clock::now();
94 for (
int i = 0; i < kAutotuneIters; ++i) gp->dispatch(
shader, gx, gy, 1);
95 const auto t1 = std::chrono::steady_clock::now();
96 return std::chrono::duration<double, std::milli>(t1 - t0).count() / kAutotuneIters;
104 std::unique_ptr<gpgpu::ComputeShader>
pass1;
105 std::unique_ptr<gpgpu::ComputeShader>
pass2;
107 std::vector<gpgpu::GpuBuffer *>
stats;
133 auto *p1 = g.
pass1.get();
135 p1->bindBuffer(i, g.
inputs[
static_cast<size_t>(i)]);
139 auto *p2 = g.
pass2.get();
141 p2->bindBuffer(i, g.
inputs[
static_cast<size_t>(i)]);
151 p2->bindBuffer(outBinding, g.
output);
165GpuProgram::GpuProgram() : impl_(new Impl()) {}
169 auto *gp = gpgpu::Gpgpu::create();
170 if (!gp || !gp->isAvailable())
return nullptr;
171 if (outputNode < 0 || outputNode >=
graph.nodeCount())
return nullptr;
174 prog->impl_->gpgpu = gp;
175 auto &
impl = *prog->impl_;
183 impl.sequence.reset(gp->newSequence());
186 impl.placeholderBuffers.clear();
187 impl.placeholderSizes.clear();
188 for (
int id = 0;
id <
graph.nodeCount(); ++
id) {
189 const auto &nd =
graph.node(
id);
190 if (opt.
nodeSlot[
static_cast<size_t>(
id)] < 0)
continue;
191 auto *buf =
impl.slotBuffer[
static_cast<size_t>(opt.
nodeSlot[
static_cast<size_t>(
id)])];
193 const int slot = nd.placeholderSlot;
194 if (slot < 0)
throw eve::Exception(
"GpuProgram: bad placeholder slot");
195 if (
int(
impl.placeholderBuffers.size()) <= slot) {
196 impl.placeholderBuffers.resize(
size_t(slot) + 1,
nullptr);
197 impl.placeholderSizes.resize(
size_t(slot) + 1, 0);
199 impl.placeholderBuffers[
static_cast<size_t>(slot)] = buf;
200 impl.placeholderSizes[
static_cast<size_t>(slot)] = nd.size;
202 if (!nd.constBytes.empty()) {
203 buf->uploadBytes(nd.constBytes.data(), nd.constBytes.size());
204 if (!nd.constScales.empty()) {
205 auto *sb =
impl.alloc(
int(nd.constScales.size()) *
int(
sizeof(
float)));
206 sb->uploadBytes(nd.constScales.data(),
207 sizeof(
float) * nd.constScales.size());
208 impl.qScalesByNode[
id] = sb;
211 buf->uploadBytes(nd.constData.data(),
sizeof(
float) *
size_t(nd.size));
218 const auto &grp = opt.
groups[
static_cast<size_t>(gi)];
222 for (
int input : grp.inputs) {
223 const int slot = opt.
nodeSlot[
static_cast<size_t>(input)];
224 if (slot < 0)
throw eve::Exception(
"GpuProgram: input without slot");
225 rt.
inputs.push_back(
impl.slotBuffer[
static_cast<size_t>(slot)]);
228 auto it =
impl.qScalesByNode.find(input);
229 if (it !=
impl.qScalesByNode.end()) rt.
qScales = it->second;
232 const int outSlot = opt.
nodeSlot[
static_cast<size_t>(grp.outputNode)];
233 if (outSlot < 0)
throw eve::Exception(
"GpuProgram: output without slot");
234 rt.
output =
impl.slotBuffer[
static_cast<size_t>(outSlot)];
244 std::unique_ptr<gpgpu::ComputeShader> naiveShader, tiledShader;
246 naiveShader.reset(gp->newShader(naive.
pass2));
247 tiledShader.reset(gp->newShader(tiled.
pass2));
251 bindKernel(naiveShader.get(), naive, rt.
inputs, rt.
output);
252 const double tNaive =
253 timeDispatch(gp, naiveShader.get(), naive.
groupsX2,
255 bindKernel(tiledShader.get(), tiled, rt.
inputs, rt.
output);
256 const double tTiled =
257 timeDispatch(gp, tiledShader.get(), tiled.
groupsX2,
259 if (tTiled < tNaive) {
261 rt.
pass2 = std::move(tiledShader);
264 rt.
pass2 = std::move(naiveShader);
268 rt.
pass2 = std::move(naiveShader);
289 impl.groups.push_back(std::move(rt));
293 const int outSlot = opt.
nodeSlot[
static_cast<size_t>(outputNode)];
294 if (outSlot < 0)
throw eve::Exception(
"GpuProgram: final output without slot");
295 impl.outputBuffer =
impl.slotBuffer[
static_cast<size_t>(outSlot)];
296 impl.outputSize =
graph.node(outputNode).size;
297 impl.outputStaging.reset(
298 gp->newBuffer(
impl.outputSize *
int(
sizeof(
float)),
"staging"));
300 }
catch (
const std::exception &e) {
301 fprintf(stderr,
"[tensor] GpuProgram::tryBuild failed: %s\n", e.what());
313 for (
size_t i = 0; i < feeds.size(); ++i) {
319 const uint64_t outBytes =
sizeof(float) *
size_t(impl_->
outputSize);
323 std::vector<float> out(
static_cast<size_t>(impl_->
outputSize));
328bool gpuReduce(
const float *data,
int size,
int op,
float &outResult) {
329 if (!
data || size <= 0)
return false;
330 ReduceKernels *kernels = getReduceKernels();
331 if (!kernels)
return false;
333 std::unique_ptr<gpgpu::GpuBuffer> in(
334 kernels->gpgpu->newBuffer(size *
int(
sizeof(
float)),
"storage"));
335 in->uploadBytes(
data,
sizeof(
float) *
size_t(size));
337 const int groups = groupsFor(size);
338 std::unique_ptr<gpgpu::GpuBuffer> partial(
339 kernels->gpgpu->newBuffer(groups *
int(
sizeof(
float)),
"storage"));
341 kernels->reduce->bindBuffer(0, in.get());
342 kernels->reduce->bindBuffer(1, partial.get());
343 kernels->reduce->setFloat(0,
float(size));
344 kernels->reduce->setFloat(1,
float(op));
345 kernels->gpgpu->dispatch(kernels->reduce, groups);
347 std::vector<float> parts(
static_cast<size_t>(groups));
348 partial->downloadBytes(parts.data(),
sizeof(
float) *
size_t(groups));
350 float acc = op == 0 ? 0.f
351 : (op == 1 ? std::numeric_limits<float>::max()
352 : -std::numeric_limits<float>::max());
353 for (
float v : parts) {
354 if (op == 0) acc +=
v;
355 else if (op == 1) acc = std::min(acc,
v);
356 else acc = std::max(acc,
v);
gpgpu::ComputeShader * reduce
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...
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
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)
bool generateKernel(const Graph &graph, const FusedGroup &group, KernelSpec &out)
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...
bool generateMatMulVariant(const Graph &graph, const FusedGroup &group, bool tiled, KernelSpec &out)
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
std::vector< gpgpu::GpuBuffer * > ownedBuffers
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< float > constScales
std::vector< uint8_t > constBytes
std::vector< int > slotSize
std::vector< int > nodeSlot
std::vector< int > groupOrder
std::vector< FusedGroup > groups