#include "tensor/Graph.h" #include "tensor/CpuKernels.h" #include "tensor/GpuBackend.h" #include "tensor/Optimizer.h" #include "tensor/Quant.h" #include "tensor/TF.h" #include "common/Exception.h" #include #include #include namespace eve::tensor { // --------------------------------------------------------------------------- // Func: tracing graph builder // --------------------------------------------------------------------------- Func::Func(TF *owner) : owner_(owner) { if (!owner_) throw eve::Exception("Func: null owner"); owner_->pushTrace(this); } Func::~Func() { if (tracing_ && owner_) owner_->popTrace(this); } GraphNode Func::makeShapeNode(OpType type, const int *dims, int rank) { GraphNode n; n.type = type; n.rank = rank; for (int i = 0; i < Tensor::kMaxRank; ++i) n.dims[i] = 0; for (int i = 0; i < rank; ++i) n.dims[i] = dims[i]; n.size = Graph::product(dims, rank); return n; } Tensor *Func::makeSymbolicFromNode(int nodeId) { const auto &n = graph_.node(nodeId); auto *t = Tensor::makeSymbolic(&graph_, nodeId, n.dims, n.rank); t->setDtype(static_cast(n.dtype)); return t; } namespace { GraphNode makeInputNode(Graph &graph, OpType type, const int *dims, int rank, int slot) { GraphNode n; n.type = type; n.rank = rank; for (int i = 0; i < Tensor::kMaxRank; ++i) n.dims[i] = 0; for (int i = 0; i < rank; ++i) n.dims[i] = dims[i]; n.size = Graph::product(dims, rank); n.placeholderSlot = slot; return n; } int normalizeAxisChecked(int axis, int rank) { if (axis < 0) axis += rank; if (axis < 0 || axis >= rank) throw eve::Exception("Func: axis out of range"); return axis; } int convOutSize(int inSize, int kernel, int stride, int pad) { const int out = (inSize + 2 * pad - kernel) / stride + 1; if (out <= 0) throw eve::Exception("Func: conv output size must be > 0"); return out; } } // namespace Tensor *Func::input1(int d0) { int d[] = {d0}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 1, placeholderCount_++)); return makeSymbolicFromNode(id); } Tensor *Func::input2(int d0, int d1) { int d[] = {d0, d1}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 2, placeholderCount_++)); return makeSymbolicFromNode(id); } Tensor *Func::input3(int d0, int d1, int d2) { int d[] = {d0, d1, d2}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 3, placeholderCount_++)); return makeSymbolicFromNode(id); } Tensor *Func::input4(int d0, int d1, int d2, int d3) { int d[] = {d0, d1, d2, d3}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 4, placeholderCount_++)); return makeSymbolicFromNode(id); } Tensor *Func::input5(int d0, int d1, int d2, int d3, int d4) { int d[] = {d0, d1, d2, d3, d4}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 5, placeholderCount_++)); return makeSymbolicFromNode(id); } Tensor *Func::input6(int d0, int d1, int d2, int d3, int d4, int d5) { int d[] = {d0, d1, d2, d3, d4, d5}; int id = graph_.addNode(makeInputNode(graph_, OpType::Placeholder, d, 6, placeholderCount_++)); return makeSymbolicFromNode(id); } void Func::setOutput(Tensor *t) { if (!t) throw eve::Exception("Func.setOutput: null"); outputNode_ = ensureNode(t); } int Func::ensureNode(const Tensor *t) { if (!t) throw eve::Exception("Func.ensureNode: null"); if (t->isSymbolic()) { if (t->graph() != &graph_) throw eve::Exception("Func: tensor from another graph"); return t->nodeId(); } t->ensureEager("capture"); auto n = makeShapeNode(OpType::Const, t->dims_, t->rank_); if (t->isQuantized()) { n.constBytes = t->qBytes(); n.constScales = t->qScales(); n.qGroup = t->qGroup(); } else { n.constData.assign(t->data(), t->data() + t->getSize()); } n.dtype = static_cast(t->dtype_); return graph_.addNode(std::move(n)); } Tensor *Func::emitFill(const int *dims, int rank, float value) { auto n = makeShapeNode(OpType::Const, dims, rank); n.constData.assign(static_cast(n.size), value); int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitUnary(OpType type, const Tensor *x) { int ix = ensureNode(x); const auto &src = graph_.node(ix); auto n = makeShapeNode(type, src.dims, src.rank); n.in0 = ix; n.dtype = src.dtype; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitUnaryScalar(OpType type, const Tensor *x, float s0, float s1) { int ix = ensureNode(x); const auto &src = graph_.node(ix); auto n = makeShapeNode(type, src.dims, src.rank); n.in0 = ix; n.s0 = s0; n.s1 = s1; n.dtype = src.dtype; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitBinary(OpType type, const Tensor *a, const Tensor *b) { int ia = ensureNode(a); int ib = ensureNode(b); const auto &na = graph_.node(ia); const auto &nb = graph_.node(ib); int od[Tensor::kMaxRank] = {}; int orank = 0; if (!kernels::broadcastShape(na.dims, na.rank, nb.dims, nb.rank, od, orank)) throw eve::Exception("Func binary: broadcast shape mismatch"); auto n = makeShapeNode(type, od, orank); n.in0 = ia; n.in1 = ib; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitTernary(OpType type, const Tensor *a, const Tensor *b, const Tensor *c) { int ia = ensureNode(a); int ib = ensureNode(b); int ic = ensureNode(c); const auto &na = graph_.node(ia); auto n = makeShapeNode(type, na.dims, na.rank); n.in0 = ia; n.in1 = ib; n.in2 = ic; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitMatMul(const Tensor *a, const Tensor *b) { int ia = ensureNode(a); int ib = ensureNode(b); const auto &na = graph_.node(ia); const auto &nb = graph_.node(ib); if (na.rank == 2 && nb.rank == 2) { if (na.dims[1] != nb.dims[0]) throw eve::Exception("Func.matmul: inner dims mismatch"); int od[] = {na.dims[0], nb.dims[1]}; auto n = makeShapeNode(OpType::MatMul, od, 2); n.in0 = ia; n.in1 = ib; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } if (na.rank == 3 && nb.rank == 3) { if (na.dims[0] != nb.dims[0] || na.dims[2] != nb.dims[1]) throw eve::Exception("Func.matmul: batched dims mismatch"); int od[] = {na.dims[0], na.dims[1], nb.dims[2]}; auto n = makeShapeNode(OpType::MatMul, od, 3); n.in0 = ia; n.in1 = ib; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } throw eve::Exception("Func.matmul: rank 2x2 or 3x3 required"); } Tensor *Func::emitTranspose(const Tensor *x) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (src.rank != 2) throw eve::Exception("Func.transpose: rank 2 required"); int order[] = {1, 0}; return emitPermute(x, order, 2); } Tensor *Func::emitPermute(const Tensor *x, const int *order, int rank) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (rank != src.rank) throw eve::Exception("Func.permute: rank mismatch"); int od[Tensor::kMaxRank] = {}; for (int k = 0; k < rank; ++k) { if (order[k] < 0 || order[k] >= rank) throw eve::Exception("Func.permute: order out of range"); od[k] = src.dims[order[k]]; } auto n = makeShapeNode(OpType::Permute, od, rank); n.in0 = ix; for (int k = 0; k < rank; ++k) n.perm[k] = order[k]; n.permRank = rank; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitReshape(const Tensor *x, const int *dims, int rank) { int ix = ensureNode(x); const auto &src = graph_.node(ix); int newSize = Graph::product(dims, rank); if (newSize != src.size) throw eve::Exception("Func.reshape: size mismatch"); auto n = makeShapeNode(OpType::Reshape, dims, rank); n.in0 = ix; n.dtype = src.dtype; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitSoftmax(const Tensor *x, int axis, bool logMode) { int ix = ensureNode(x); const auto &src = graph_.node(ix); axis = normalizeAxisChecked(axis, src.rank); auto n = makeShapeNode(logMode ? OpType::LogSoftmax : OpType::Softmax, src.dims, src.rank); n.in0 = ix; n.i0 = axis; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } namespace { void checkParamVector(const GraphNode &src, int cols, const char *what) { if (src.rank == 1 && src.dims[0] == cols) return; if (src.rank == 2 && src.dims[0] == 1 && src.dims[1] == cols) return; throw eve::Exception("Func.%s: expected shape [%d]", what, cols); } } // namespace Tensor *Func::emitLayerNorm(const Tensor *x, const Tensor *scale, const Tensor *bias, float eps) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (src.rank < 1) throw eve::Exception("Func.layernorm: rank >= 1 required"); const int cols = src.dims[src.rank - 1]; auto n = makeShapeNode(OpType::LayerNorm, src.dims, src.rank); n.in0 = ix; n.s0 = eps; if (scale) { int is = ensureNode(scale); checkParamVector(graph_.node(is), cols, "layernorm scale"); n.in1 = is; } if (bias) { int ib = ensureNode(bias); checkParamVector(graph_.node(ib), cols, "layernorm bias"); n.in2 = ib; } int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitRMSNorm(const Tensor *x, const Tensor *scale, float eps) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (src.rank < 1) throw eve::Exception("Func.rmsnorm: rank >= 1 required"); const int cols = src.dims[src.rank - 1]; auto n = makeShapeNode(OpType::RMSNorm, src.dims, src.rank); n.in0 = ix; n.s0 = eps; if (scale) { int is = ensureNode(scale); checkParamVector(graph_.node(is), cols, "rmsnorm scale"); n.in1 = is; } int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitConv1d(const Tensor *x, const Tensor *w, const Tensor *bias, int stride, int pad) { int ix = ensureNode(x); int iw = ensureNode(w); const auto &nx = graph_.node(ix); const auto &nw = graph_.node(iw); if (nx.rank != 3 || nw.rank != 3) throw eve::Exception("Func.conv1d: rank 3 required"); if (nx.dims[1] != nw.dims[1]) throw eve::Exception("Func.conv1d: channel mismatch"); const int OL = convOutSize(nx.dims[2], nw.dims[2], stride, pad); int od[] = {nx.dims[0], nw.dims[0], OL}; auto n = makeShapeNode(OpType::Conv1d, od, 3); n.in0 = ix; n.in1 = iw; n.i0 = stride; n.i1 = pad; if (bias) { int ib = ensureNode(bias); const auto &nb = graph_.node(ib); if (nb.rank != 1 || nb.dims[0] != n.dims[1]) throw eve::Exception("Func.conv1d: bias shape mismatch"); n.in2 = ib; } int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitConv2d(const Tensor *x, const Tensor *w, const Tensor *bias, int stride, int pad) { int ix = ensureNode(x); int iw = ensureNode(w); const auto &nx = graph_.node(ix); const auto &nw = graph_.node(iw); if (nx.rank != 4 || nw.rank != 4) throw eve::Exception("Func.conv2d: rank 4 required"); if (nx.dims[1] != nw.dims[1]) throw eve::Exception("Func.conv2d: channel mismatch"); const int OH = convOutSize(nx.dims[2], nw.dims[2], stride, pad); const int OW = convOutSize(nx.dims[3], nw.dims[3], stride, pad); int od[] = {nx.dims[0], nw.dims[0], OH, OW}; auto n = makeShapeNode(OpType::Conv2d, od, 4); n.in0 = ix; n.in1 = iw; n.i0 = stride; n.i1 = pad; if (bias) { int ib = ensureNode(bias); const auto &nb = graph_.node(ib); if (nb.rank != 1 || nb.dims[0] != n.dims[1]) throw eve::Exception("Func.conv2d: bias shape mismatch"); n.in2 = ib; } int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitPool(OpType type, const Tensor *x, int ksize, int stride, int pad) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (src.rank != 4) throw eve::Exception("Func.pool: rank 4 required"); const int OH = convOutSize(src.dims[2], ksize, stride, pad); const int OW = convOutSize(src.dims[3], ksize, stride, pad); int od[] = {src.dims[0], src.dims[1], OH, OW}; auto n = makeShapeNode(type, od, 4); n.in0 = ix; n.i0 = ksize; n.i1 = stride; n.i2 = pad; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitEmbedding(const Tensor *table, const Tensor *indices) { int it = ensureNode(table); int ii = ensureNode(indices); const auto &nt = graph_.node(it); const auto &ni = graph_.node(ii); if (nt.rank != 2) throw eve::Exception("Func.embedding: table rank 2 required"); if (ni.rank < 1 || ni.rank + 1 > Tensor::kMaxRank) throw eve::Exception("Func.embedding: index rank out of range"); int od[Tensor::kMaxRank] = {}; for (int k = 0; k < ni.rank; ++k) od[k] = ni.dims[k]; od[ni.rank] = nt.dims[1]; auto n = makeShapeNode(OpType::Embedding, od, ni.rank + 1); n.in0 = it; n.in1 = ii; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitConcat(const Tensor *const *ins, int n, int axis) { if (!ins || n < 2 || n > 4) throw eve::Exception("Func.concat: 2..4 inputs required"); int ids[4] = {}; const GraphNode *ns[4] = {}; // Importing an eager input may reallocate the graph's node array. for (int k = 0; k < n; ++k) ids[k] = ensureNode(ins[k]); for (int k = 0; k < n; ++k) { ns[k] = &graph_.node(ids[k]); if (ns[k]->rank != ns[0]->rank) throw eve::Exception("Func.concat: rank mismatch"); } axis = normalizeAxisChecked(axis, ns[0]->rank); int od[Tensor::kMaxRank] = {}; for (int k = 0; k < ns[0]->rank; ++k) { if (k == axis) { int total = 0; for (int t = 0; t < n; ++t) total += ns[t]->dims[k]; od[k] = total; } else { od[k] = ns[0]->dims[k]; for (int t = 1; t < n; ++t) if (ns[t]->dims[k] != od[k]) throw eve::Exception("Func.concat: dims mismatch on axis %d", k); } } auto g = makeShapeNode(OpType::Concat, od, ns[0]->rank); g.in0 = ids[0]; g.in1 = ids[1]; if (n > 2) g.in2 = ids[2]; if (n > 3) g.in3 = ids[3]; g.i0 = axis; int id = graph_.addNode(std::move(g)); return makeSymbolicFromNode(id); } Tensor *Func::emitSlice(const Tensor *x, int axis, int begin, int end) { int ix = ensureNode(x); const auto &src = graph_.node(ix); axis = normalizeAxisChecked(axis, src.rank); if (begin < 0 || end < begin || end > src.dims[axis]) throw eve::Exception("Func.slice: range out of bounds"); int od[Tensor::kMaxRank] = {}; for (int k = 0; k < src.rank; ++k) od[k] = src.dims[k]; od[axis] = end - begin; auto n = makeShapeNode(OpType::Slice, od, src.rank); n.in0 = ix; n.i0 = axis; n.i1 = begin; n.i2 = end; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitReduce(OpType type, const Tensor *x, int axis, bool keepDims) { int ix = ensureNode(x); const auto &src = graph_.node(ix); axis = normalizeAxisChecked(axis, src.rank); int od[Tensor::kMaxRank] = {}; int orank = src.rank; for (int k = 0; k < src.rank; ++k) od[k] = src.dims[k]; if (keepDims) { od[axis] = 1; } else { if (orank == 1) { od[0] = 1; // Tensor rank must stay >= 1 } else { for (int k = axis; k < orank - 1; ++k) od[k] = od[k + 1]; od[orank - 1] = 0; --orank; } } auto n = makeShapeNode(type, od, orank); n.in0 = ix; n.i0 = axis; n.i1 = keepDims ? 1 : 0; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitArgMax(const Tensor *x, int axis, bool keepDims) { int ix = ensureNode(x); const auto &src = graph_.node(ix); axis = normalizeAxisChecked(axis, src.rank); int od[Tensor::kMaxRank] = {}; int orank = src.rank; for (int k = 0; k < src.rank; ++k) od[k] = src.dims[k]; if (keepDims) { od[axis] = 1; } else { if (orank == 1) { od[0] = 1; } else { for (int k = axis; k < orank - 1; ++k) od[k] = od[k + 1]; od[orank - 1] = 0; --orank; } } auto n = makeShapeNode(OpType::ArgMax, od, orank); n.in0 = ix; n.i0 = axis; n.i1 = keepDims ? 1 : 0; n.dtype = static_cast(DType::Int32); int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitCast(const Tensor *x, DType dtype) { int ix = ensureNode(x); const auto &src = graph_.node(ix); auto n = makeShapeNode(OpType::Cast, src.dims, src.rank); n.in0 = ix; n.dtype = static_cast(dtype); int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitSdpa(const Tensor *q, const Tensor *k, const Tensor *v, const Tensor *mask, float scale) { int iq = ensureNode(q); int ik = ensureNode(k); int iv = ensureNode(v); const int im = mask ? ensureNode(mask) : -1; const auto &nq = graph_.node(iq); const auto &nk = graph_.node(ik); const auto &nv = graph_.node(iv); if (nq.rank != 4 || nk.rank != 4 || nv.rank != 4) throw eve::Exception("Func.sdpa: rank 4 required"); if (nq.dims[0] != nk.dims[0] || nq.dims[1] != nk.dims[1] || nq.dims[3] != nk.dims[3] || nk.dims[2] != nv.dims[2] || nq.dims[3] != nv.dims[3]) throw eve::Exception("Func.sdpa: q/k/v shape mismatch"); auto n = makeShapeNode(OpType::ScaledDotProductAttention, nq.dims, 4); n.in0 = iq; n.in1 = ik; n.in2 = iv; n.s0 = scale; if (mask) { const auto &nm = graph_.node(im); if (nm.rank != 4 || nm.dims[0] != nq.dims[0] || nm.dims[1] != nq.dims[1] || nm.dims[2] != nq.dims[2] || nm.dims[3] != nk.dims[2]) throw eve::Exception("Func.sdpa: mask shape mismatch"); n.in3 = im; } int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } Tensor *Func::emitResize2d(const Tensor *x, int outH, int outW, int mode) { int ix = ensureNode(x); const auto &src = graph_.node(ix); if (src.rank != 4) throw eve::Exception("Func.resize2d: rank 4 required"); if (outH <= 0 || outW <= 0) throw eve::Exception("Func.resize2d: bad output size"); int od[] = {src.dims[0], src.dims[1], outH, outW}; auto n = makeShapeNode(OpType::Resize2d, od, 4); n.in0 = ix; n.i0 = mode; int id = graph_.addNode(std::move(n)); return makeSymbolicFromNode(id); } // --------------------------------------------------------------------------- // CompiledFunction // --------------------------------------------------------------------------- CompiledFunction::CompiledFunction() = default; CompiledFunction::~CompiledFunction() = default; CompiledFunction *Func::compile() { if (outputNode_ < 0) throw eve::Exception("Func.compile: setOutput required"); tracing_ = false; if (owner_) owner_->popTrace(this); return CompiledFunction::fromFunc(this); } CompiledFunction *CompiledFunction::fromFunc(Func *fn) { if (!fn) throw eve::Exception("CompiledFunction: null func"); auto *cf = new CompiledFunction(); cf->graph_ = fn->graph(); // copy cf->outputNode_ = fn->outputNode(); cf->placeholderCount_ = fn->placeholderCount(); cf->device_ = "cpu"; cf->optimized_ = std::make_unique(optimizeGraph(cf->graph_, cf->outputNode_)); cf->order_ = cf->optimized_->order; // Best-effort: run on GPU via eve::gpgpu compute shaders when a Vulkan // device is available. Falls back to the CPU interpreter below otherwise. try { cf->gpuProgram_.reset(GpuProgram::tryBuild(cf->graph_, *cf->optimized_, cf->outputNode_)); } catch (...) { cf->gpuProgram_.reset(); } if (cf->gpuProgram_) cf->device_ = "gpu"; return cf; } Tensor *CompiledFunction::run0() { return runWithFeeds(nullptr, 0); } Tensor *CompiledFunction::run1(Tensor *in0) { Tensor *feeds[] = {in0}; return runWithFeeds(feeds, 1); } Tensor *CompiledFunction::run2(Tensor *in0, Tensor *in1) { Tensor *feeds[] = {in0, in1}; return runWithFeeds(feeds, 2); } Tensor *CompiledFunction::run3(Tensor *in0, Tensor *in1, Tensor *in2) { Tensor *feeds[] = {in0, in1, in2}; return runWithFeeds(feeds, 3); } Tensor *CompiledFunction::run4(Tensor *in0, Tensor *in1, Tensor *in2, Tensor *in3) { Tensor *feeds[] = {in0, in1, in2, in3}; return runWithFeeds(feeds, 4); } Tensor *CompiledFunction::run5(Tensor *in0, Tensor *in1, Tensor *in2, Tensor *in3, Tensor *in4) { Tensor *feeds[] = {in0, in1, in2, in3, in4}; return runWithFeeds(feeds, 5); } Tensor *CompiledFunction::run6(Tensor *in0, Tensor *in1, Tensor *in2, Tensor *in3, Tensor *in4, Tensor *in5) { Tensor *feeds[] = {in0, in1, in2, in3, in4, in5}; return runWithFeeds(feeds, 6); } Tensor *CompiledFunction::runWithFeeds(Tensor *const *feeds, int nFeeds) { if (nFeeds != placeholderCount_) throw eve::Exception("CompiledFunction.run: expected %d feeds, got %d", placeholderCount_, nFeeds); for (int i = 0; i < nFeeds; ++i) { if (!feeds[i]) throw eve::Exception("CompiledFunction.run: null feed"); feeds[i]->ensureEager("run"); } const int n = graph_.nodeCount(); // Validate placeholder shapes (shared by the GPU and CPU execution paths). for (int i = 0; i < n; ++i) { const auto &nd = graph_.node(i); if (nd.type != OpType::Placeholder) continue; const int slot = nd.placeholderSlot; if (slot < 0 || slot >= nFeeds) throw eve::Exception("CompiledFunction: bad placeholder slot"); Tensor *feed = feeds[slot]; if (feed->getRank() != nd.rank || feed->getSize() != nd.size) throw eve::Exception("CompiledFunction: feed shape mismatch"); for (int a = 0; a < nd.rank; ++a) if (feed->getDim(a) != nd.dims[a]) throw eve::Exception("CompiledFunction: feed shape mismatch"); } const auto &outN = graph_.node(outputNode_); if (gpuProgram_) { std::vector ptrs(static_cast(nFeeds)); for (int i = 0; i < nFeeds; ++i) ptrs[static_cast(i)] = feeds[i]->data(); std::vector result = gpuProgram_->run(ptrs); auto *out = new Tensor(static_cast(outN.dtype), outN.dims, outN.rank); if (int(result.size()) != out->getSize()) throw eve::Exception("CompiledFunction: gpu output size mismatch"); std::memcpy(out->data(), result.data(), sizeof(float) * static_cast(out->getSize())); return out; } std::vector> bufs(static_cast(n)); for (int i = 0; i < n; ++i) { const auto &nd = graph_.node(i); if (nd.type != OpType::Placeholder) continue; Tensor *feed = feeds[nd.placeholderSlot]; bufs[static_cast(i)].assign(feed->data(), feed->data() + feed->getSize()); } for (int nodeId : order_) executeNode(nodeId, bufs); auto *out = new Tensor(static_cast(outN.dtype), outN.dims, outN.rank); const auto &src = bufs[static_cast(outputNode_)]; if (int(src.size()) != out->getSize()) throw eve::Exception("CompiledFunction: output size mismatch"); std::memcpy(out->data(), src.data(), sizeof(float) * static_cast(out->getSize())); return out; } void CompiledFunction::executeNode(int nodeId, std::vector> &bufs) const { const auto &nd = graph_.node(nodeId); auto &out = bufs[static_cast(nodeId)]; const auto in = [&](int id) -> const std::vector & { return bufs[static_cast(id)]; }; switch (nd.type) { case OpType::Placeholder: return; // already filled case OpType::Const: if (!nd.constBytes.empty()) { out.assign(static_cast(nd.size), 0.f); q::dequantizeAll(static_cast(nd.dtype), nd.constBytes.data(), nd.constScales.data(), nd.qGroup, nd.size, out.data()); } else { out = nd.constData; } return; case OpType::Add: case OpType::Sub: case OpType::Multiply: case OpType::Divide: { const auto &na = graph_.node(nd.in0); const auto &nb = graph_.node(nd.in1); out.resize(static_cast(nd.size)); kernels::binaryOp(nd.type, in(nd.in0).data(), na.dims, na.rank, in(nd.in1).data(), nb.dims, nb.rank, out.data(), nd.dims, nd.rank); return; } case OpType::Neg: case OpType::Abs: case OpType::Sqrt: case OpType::Exp: case OpType::Log: case OpType::Sin: case OpType::Cos: case OpType::Tanh: case OpType::Relu: case OpType::Sigmoid: case OpType::Gelu: case OpType::Silu: case OpType::AddScalar: case OpType::SubScalar: case OpType::MulScalar: case OpType::DivScalar: case OpType::PowScalar: case OpType::Clamp: case OpType::MaximumScalar: case OpType::MinimumScalar: { out.resize(static_cast(nd.size)); kernels::unaryOp(nd.type, in(nd.in0).data(), nd.size, out.data(), nd.s0, nd.s1); return; } case OpType::Where: { const auto &c = in(nd.in0); const auto &a = in(nd.in1); const auto &b = in(nd.in2); out.resize(static_cast(nd.size)); for (int i = 0; i < nd.size; ++i) out[static_cast(i)] = c[static_cast(i)] > 0.5f ? a[static_cast(i)] : b[static_cast(i)]; return; } case OpType::MatMul: { const auto &A = graph_.node(nd.in0); const auto &B = graph_.node(nd.in1); const auto &a = in(nd.in0); const auto &b = in(nd.in1); out.assign(static_cast(nd.size), 0.f); if (nd.rank == 2) { const int m = A.dims[0], k = A.dims[1], n = B.dims[1]; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { double acc = 0.0; for (int t = 0; t < k; ++t) acc += double(a[static_cast(i * k + t)]) * double(b[static_cast(t * n + j)]); out[static_cast(i * n + j)] = float(acc); } } } else { const int batch = A.dims[0], m = A.dims[1], k = A.dims[2], n = B.dims[2]; for (int bb = 0; bb < batch; ++bb) { const float *ap = a.data() + size_t(bb) * m * k; const float *bp = b.data() + size_t(bb) * k * n; float *cp = out.data() + size_t(bb) * m * n; for (int i = 0; i < m; ++i) for (int j = 0; j < n; ++j) { double acc = 0.0; for (int t = 0; t < k; ++t) acc += double(ap[i * k + t]) * double(bp[t * n + j]); cp[i * n + j] = float(acc); } } } return; } case OpType::Transpose: case OpType::Permute: { const auto &X = graph_.node(nd.in0); out.resize(static_cast(nd.size)); int order[Tensor::kMaxRank] = {}; for (int k = 0; k < nd.rank; ++k) order[k] = nd.type == OpType::Transpose ? (k == 0 ? 1 : 0) : nd.perm[k]; kernels::permute(in(nd.in0).data(), X.dims, nd.rank, order, out.data(), nd.dims); return; } case OpType::Reshape: case OpType::Flatten: case OpType::Cast: out = in(nd.in0); return; case OpType::Softmax: case OpType::LogSoftmax: { out.resize(static_cast(nd.size)); kernels::softmax(in(nd.in0).data(), nd.dims, nd.rank, nd.i0, nd.type == OpType::LogSoftmax, out.data()); return; } case OpType::LayerNorm: { out.resize(static_cast(nd.size)); const int cols = nd.dims[nd.rank - 1]; const int rows = nd.size / cols; kernels::layernorm(in(nd.in0).data(), rows, cols, nd.in1 >= 0 ? in(nd.in1).data() : nullptr, nd.in2 >= 0 ? in(nd.in2).data() : nullptr, nd.s0, out.data()); return; } case OpType::RMSNorm: { out.resize(static_cast(nd.size)); const int cols = nd.dims[nd.rank - 1]; const int rows = nd.size / cols; kernels::rmsnorm(in(nd.in0).data(), rows, cols, nd.in1 >= 0 ? in(nd.in1).data() : nullptr, nd.s0, out.data()); return; } case OpType::Conv1d: case OpType::Conv2d: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); const auto &W = graph_.node(nd.in1); if (nd.type == OpType::Conv1d) kernels::conv1d(in(nd.in0).data(), X.dims, in(nd.in1).data(), W.dims, nd.in2 >= 0 ? in(nd.in2).data() : nullptr, nd.i0, nd.i1, out.data()); else kernels::conv2d(in(nd.in0).data(), X.dims, in(nd.in1).data(), W.dims, nd.in2 >= 0 ? in(nd.in2).data() : nullptr, nd.i0, nd.i1, out.data()); return; } case OpType::MaxPool2d: case OpType::AvgPool2d: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); if (nd.type == OpType::MaxPool2d) kernels::maxpool2d(in(nd.in0).data(), X.dims, nd.i0, nd.i1, nd.i2, out.data()); else kernels::avgpool2d(in(nd.in0).data(), X.dims, nd.i0, nd.i1, nd.i2, out.data()); return; } case OpType::Embedding: { out.resize(static_cast(nd.size)); const auto &T = graph_.node(nd.in0); const auto &I = graph_.node(nd.in1); kernels::embedding(in(nd.in0).data(), T.dims[0], T.dims[1], in(nd.in1).data(), I.size, out.data()); return; } case OpType::Concat: { out.resize(static_cast(nd.size)); const float *ins[4] = {}; const int *dims[4] = {}; int ranks[4] = {}; int n = 2; if (nd.in3 >= 0) n = 4; else if (nd.in2 >= 0) n = 3; const int ids[4] = {nd.in0, nd.in1, nd.in2, nd.in3}; for (int k = 0; k < n; ++k) { ins[k] = in(ids[k]).data(); dims[k] = graph_.node(ids[k]).dims; ranks[k] = graph_.node(ids[k]).rank; } kernels::concat(ins, dims, ranks, n, nd.i0, out.data(), nd.dims, nd.rank); return; } case OpType::Slice: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); kernels::sliceOp(in(nd.in0).data(), X.dims, X.rank, nd.i0, nd.i1, nd.i2, out.data(), nd.dims, nd.rank); return; } case OpType::ReduceSum: case OpType::ReduceMean: case OpType::ReduceMin: case OpType::ReduceMax: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); kernels::reduceAxis(nd.type, in(nd.in0).data(), X.dims, X.rank, nd.i0, out.data(), nd.dims, nd.rank); return; } case OpType::ArgMax: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); kernels::argmax(in(nd.in0).data(), X.dims, X.rank, nd.i0, out.data(), nd.dims, nd.rank); return; } case OpType::ScaledDotProductAttention: { out.resize(static_cast(nd.size)); const auto &Q = graph_.node(nd.in0); const auto &K = graph_.node(nd.in1); const int B = Q.dims[0], H = Q.dims[1], T = Q.dims[2], D = Q.dims[3]; const int S = K.dims[2]; kernels::sdpa(in(nd.in0).data(), in(nd.in1).data(), in(nd.in2).data(), nd.in3 >= 0 ? in(nd.in3).data() : nullptr, B, H, T, S, D, nd.s0, out.data()); return; } case OpType::Resize2d: { out.resize(static_cast(nd.size)); const auto &X = graph_.node(nd.in0); kernels::resize2d(in(nd.in0).data(), X.dims, nd.dims[3], nd.dims[2], nd.i0, out.data()); return; } } } } // namespace eve::tensor