载入中...
搜索中...
未找到
GpuBackend.cpp
浏览该文件的文档.
1#include "tensor/GpuBackend.h"
2#include "tensor/Graph.h"
3#include "tensor/KernelGen.h"
4#include "tensor/Optimizer.h"
5
7#include "gpgpu/Gpgpu.h"
8#include "gpgpu/GpuBuffer.h"
9#include "gpgpu/Sequence.h"
10
11#include "common/Exception.h"
12
13#include <algorithm>
14#include <chrono>
15#include <cstdio>
16#include <cstring>
17#include <limits>
18#include <map>
19#include <memory>
20#include <vector>
21
22namespace eve::tensor {
23namespace {
24
25constexpr int kLocalSize = 256;
26constexpr int kAutotuneIters = 5;
27
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];
34void main() {
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;
41 barrier();
42 for (uint s = 128u; s > 0u; s >>= 1u) {
43 if (tid < s) {
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]);
47 }
48 barrier();
49 }
50 if (tid == 0u) partial[gl_WorkGroupID.x] = sdata[0];
51}
52)";
53
54struct ReduceKernels {
55 gpgpu::Gpgpu *gpgpu = nullptr;
56 gpgpu::ComputeShader *reduce = nullptr;
57};
58
60ReduceKernels *getReduceKernels() {
61 static ReduceKernels *kernels = nullptr;
62 static bool failed = false;
63 if (kernels) return kernels;
64 if (failed) return nullptr;
65 try {
66 auto *gp = gpgpu::Gpgpu::create();
67 if (!gp || !gp->isAvailable()) {
68 failed = true;
69 return nullptr;
70 }
71 auto *k = new ReduceKernels();
72 k->gpgpu = gp;
73 k->reduce = gp->newShader(kReduceGlsl);
74 kernels = k;
75 return kernels;
76 } catch (...) {
77 failed = true;
78 return nullptr;
79 }
80}
81
82int groupsFor(int count) { return (count + kLocalSize - 1) / kLocalSize; }
83
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);
90}
91
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;
97}
98
99} // namespace
100
104 std::unique_ptr<gpgpu::ComputeShader> pass1;
105 std::unique_ptr<gpgpu::ComputeShader> pass2;
106 std::vector<gpgpu::GpuBuffer *> inputs; // one per group input node
107 std::vector<gpgpu::GpuBuffer *> stats; // statsCount working buffers
108 gpgpu::GpuBuffer *qScales = nullptr; // per-group scales (int8/int4)
110 };
111
112 gpgpu::Gpgpu *gpgpu = nullptr;
113 std::vector<GroupRuntime> groups;
114 std::vector<gpgpu::GpuBuffer *> slotBuffer; // arena slot -> buffer
115 std::vector<gpgpu::GpuBuffer *> placeholderBuffers;
116 std::vector<int> placeholderSizes;
117 std::vector<gpgpu::GpuBuffer *> ownedBuffers; // every allocated buffer (cleanup)
118 std::map<int, gpgpu::GpuBuffer *> qScalesByNode; // quantized const node -> scales
120 int outputSize = 0;
121 std::unique_ptr<gpgpu::Sequence> sequence;
122 std::unique_ptr<gpgpu::GpuBuffer> outputStaging;
123
124 gpgpu::GpuBuffer *alloc(int byteSize) {
125 auto *buf = gpgpu->newBuffer(byteSize, "storage");
126 ownedBuffers.push_back(buf);
127 return buf;
128 }
129
131 void bindGroup(const GroupRuntime &g) const {
132 if (g.spec.twoPass) {
133 auto *p1 = g.pass1.get();
134 for (int i = 0; i < g.spec.inputsReadPass1; ++i)
135 p1->bindBuffer(i, g.inputs[static_cast<size_t>(i)]);
136 for (int s = 0; s < g.spec.statsCount; ++s)
137 p1->bindBuffer(g.spec.inputsReadPass1 + s, g.stats[static_cast<size_t>(s)]);
138 }
139 auto *p2 = g.pass2.get();
140 for (int i = 0; i < g.spec.inputCount; ++i)
141 p2->bindBuffer(i, g.inputs[static_cast<size_t>(i)]);
142 if (g.spec.scalesBinding >= 0 && g.qScales)
143 p2->bindBuffer(g.spec.scalesBinding, g.qScales);
144 const int outBinding = g.spec.outputBinding >= 0 ? g.spec.outputBinding
145 : g.spec.inputCount;
146 if (g.spec.twoPass) {
147 for (int s = 0; s < g.spec.statsCount; ++s)
148 p2->bindBuffer(g.spec.inputCount + s, g.stats[static_cast<size_t>(s)]);
149 p2->bindBuffer(g.spec.inputCount + g.spec.statsCount, g.output);
150 } else {
151 p2->bindBuffer(outBinding, g.output);
152 }
153 }
154
156 void recordGroup(const GroupRuntime &g, gpgpu::Sequence *seq) const {
157 if (g.spec.twoPass)
158 seq->recordDispatch(g.pass1.get(), g.spec.groupsX1, g.spec.groupsY1,
159 g.spec.groupsZ1);
160 seq->recordDispatch(g.pass2.get(), g.spec.groupsX2, g.spec.groupsY2,
161 g.spec.groupsZ2);
162 }
163};
164
165GpuProgram::GpuProgram() : impl_(new Impl()) {}
166GpuProgram::~GpuProgram() { delete impl_; }
167
168GpuProgram *GpuProgram::tryBuild(const Graph &graph, const OptimizedGraph &opt, int outputNode) {
169 auto *gp = gpgpu::Gpgpu::create();
170 if (!gp || !gp->isAvailable()) return nullptr;
171 if (outputNode < 0 || outputNode >= graph.nodeCount()) return nullptr;
172
173 auto *prog = new GpuProgram();
174 prog->impl_->gpgpu = gp;
175 auto &impl = *prog->impl_;
176
177 try {
178 // arena buffers: one per memory-plan slot
179 impl.slotBuffer.resize(opt.slotSize.size(), nullptr);
180 for (size_t s = 0; s < opt.slotSize.size(); ++s)
181 impl.slotBuffer[s] = impl.alloc(opt.slotSize[s] * int(sizeof(float)));
182
183 impl.sequence.reset(gp->newSequence());
184
185 // placeholder / const uploads
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)])];
192 if (nd.type == OpType::Placeholder) {
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);
198 }
199 impl.placeholderBuffers[static_cast<size_t>(slot)] = buf;
200 impl.placeholderSizes[static_cast<size_t>(slot)] = nd.size;
201 } else if (nd.type == OpType::Const) {
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;
209 }
210 } else {
211 buf->uploadBytes(nd.constData.data(), sizeof(float) * size_t(nd.size));
212 }
213 }
214 }
215
216 // per-group kernels (topological execution order)
217 for (int gi : opt.groupOrder) {
218 const auto &grp = opt.groups[static_cast<size_t>(gi)];
219 if (grp.kind == GroupKind::Alias) continue; // pure buffer alias
220
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)]);
226 const GraphNode &inN = graph.node(input);
227 if (!inN.constBytes.empty() && !inN.constScales.empty()) {
228 auto it = impl.qScalesByNode.find(input);
229 if (it != impl.qScalesByNode.end()) rt.qScales = it->second;
230 }
231 }
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)];
235
236 KernelSpec spec;
237 if (grp.kind == GroupKind::MatMul) {
238 const GraphNode &mm = graph.node(grp.nodes.front());
239 KernelSpec naive;
240 if (!generateMatMulVariant(graph, grp, false, naive))
241 throw eve::Exception("GpuProgram: matmul codegen failed");
242 if (mm.rank == 2) {
243 KernelSpec tiled;
244 std::unique_ptr<gpgpu::ComputeShader> naiveShader, tiledShader;
245 if (generateMatMulVariant(graph, grp, true, tiled)) {
246 naiveShader.reset(gp->newShader(naive.pass2));
247 tiledShader.reset(gp->newShader(tiled.pass2));
248 try {
249 // Time with the real arena buffers bound: dispatching
250 // unbound kernels on the 4-byte dummy SSBO faults.
251 bindKernel(naiveShader.get(), naive, rt.inputs, rt.output);
252 const double tNaive =
253 timeDispatch(gp, naiveShader.get(), naive.groupsX2,
254 naive.groupsY2);
255 bindKernel(tiledShader.get(), tiled, rt.inputs, rt.output);
256 const double tTiled =
257 timeDispatch(gp, tiledShader.get(), tiled.groupsX2,
258 tiled.groupsY2);
259 if (tTiled < tNaive) {
260 spec = tiled;
261 rt.pass2 = std::move(tiledShader);
262 } else {
263 spec = naive;
264 rt.pass2 = std::move(naiveShader);
265 }
266 } catch (...) {
267 spec = naive;
268 rt.pass2 = std::move(naiveShader);
269 }
270 } else {
271 spec = naive;
272 rt.pass2.reset(gp->newShader(naive.pass2));
273 }
274 } else {
275 spec = naive;
276 rt.pass2.reset(gp->newShader(naive.pass2));
277 }
278 } else {
279 if (!generateKernel(graph, grp, spec))
280 throw eve::Exception("GpuProgram: kernel codegen failed");
281 rt.pass2.reset(gp->newShader(spec.pass2));
282 if (spec.twoPass) rt.pass1.reset(gp->newShader(spec.pass1));
283 }
284 rt.spec = spec;
285
286 for (int s = 0; s < spec.statsCount; ++s)
287 rt.stats.push_back(impl.alloc(spec.statsSize * int(sizeof(float))));
288 impl.bindGroup(rt);
289 impl.groups.push_back(std::move(rt));
290 }
291
292 // final output
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"));
299 return prog;
300 } catch (const std::exception &e) {
301 fprintf(stderr, "[tensor] GpuProgram::tryBuild failed: %s\n", e.what());
302 delete prog;
303 return nullptr;
304 } catch (...) {
305 delete prog;
306 return nullptr;
307 }
308}
309
310std::vector<float> GpuProgram::run(const std::vector<const float *> &feeds) const {
311 gpgpu::Sequence *seq = impl_->sequence.get();
312 seq->begin();
313 for (size_t i = 0; i < feeds.size(); ++i) {
314 if (i >= impl_->placeholderBuffers.size() || !impl_->placeholderBuffers[i]) continue;
315 seq->recordUpload(impl_->placeholderBuffers[i], feeds[i],
316 sizeof(float) * size_t(impl_->placeholderSizes[i]));
317 }
318 for (const auto &g : impl_->groups) impl_->recordGroup(g, seq);
319 const uint64_t outBytes = sizeof(float) * size_t(impl_->outputSize);
320 seq->recordDownload(impl_->outputBuffer, impl_->outputStaging.get(), outBytes);
321 seq->submit();
322
323 std::vector<float> out(static_cast<size_t>(impl_->outputSize));
324 impl_->outputStaging->downloadBytes(out.data(), outBytes);
325 return out;
326}
327
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;
332 try {
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));
336
337 const int groups = groupsFor(size);
338 std::unique_ptr<gpgpu::GpuBuffer> partial(
339 kernels->gpgpu->newBuffer(groups * int(sizeof(float)), "storage"));
340
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);
346
347 std::vector<float> parts(static_cast<size_t>(groups));
348 partial->downloadBytes(parts.data(), sizeof(float) * size_t(groups));
349
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);
357 }
358 outResult = acc;
359 return true;
360 } catch (...) {
361 return false;
362 }
363}
364
365} // namespace eve::tensor
void * impl
std::string id
gpgpu::ComputeShader * reduce
gpgpu::Gpgpu * gpgpu
const Graph & graph
Shader * shader
Light2D::Data * data
int v
uint32_t s
Definition Weather.cpp:28
GPGPU module — compute shaders + storage buffers via the active Graphics backend. Uses the graphics q...
Definition Gpgpu.h:20
GpuBuffer * newBuffer(int byteSize, const std::string &usage="storage")
Allocate a GPU buffer. usage: "storage" (SSBO, device-local) | "staging" (host-visible transfer).
Definition Gpgpu.cpp:92
Backend-agnostic GPU buffer for compute (storage) or CPU staging transfers. Squirrel-owned; derived c...
Definition GpuBuffer.h:16
void recordUpload(GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset=0)
Definition Sequence.cpp:66
void recordDownload(GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset=0)
Definition Sequence.cpp:71
void recordDispatch(ComputeShader *shader, int groupsX, int groupsY=1, int groupsZ=1)
Definition Sequence.cpp:76
GPU execution of a compiled tensor Graph via generated compute shaders.
Definition GpuBackend.h:26
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)
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
Definition Graph.h:102
std::vector< uint8_t > constBytes
Definition Graph.h:101
std::vector< int > slotSize
Definition Optimizer.h:82
std::vector< int > nodeSlot
Definition Optimizer.h:80
std::vector< int > groupOrder
Definition Optimizer.h:78
std::vector< FusedGroup > groups
Definition Optimizer.h:76