From 0e0681914e9ebbcb7505dac6970a82a4cf78dd5b Mon Sep 17 00:00:00 2001 From: ljc66d <1814007452@qq.com> Date: Wed, 12 Aug 2026 23:56:31 +0800 Subject: [PATCH 1/2] Add files via upload --- src/core/allocator.cc | 190 +++++---- src/core/data_type.cc | 44 +-- src/core/graph.cc | 676 +++++++++++++++++++++----------- src/core/op_type.cc | 64 +-- src/core/operator.cc | 170 ++++---- src/core/runtime.cc | 72 ++-- src/core/tensor.cc | 232 +++++------ src/kernels/cpu/concat.cc | 128 +++--- src/kernels/cpu/element_wise.cc | 238 +++++------ src/kernels/cpu/transpose.cc | 120 +++--- src/kernels/cpu/unary.cc | 210 +++++----- src/operators/concat.cc | 78 ++-- src/operators/element_wise.cc | 66 ++-- src/operators/matmul.cc | 95 +++-- src/operators/transpose.cc | 103 ++--- src/operators/unary.cc | 296 +++++++------- src/utils/exception.cc | 10 +- src/utils/operator_utils.cc | 149 +++---- 18 files changed, 1629 insertions(+), 1312 deletions(-) diff --git a/src/core/allocator.cc b/src/core/allocator.cc index ff593aef..9230d014 100644 --- a/src/core/allocator.cc +++ b/src/core/allocator.cc @@ -1,69 +1,121 @@ -#include "core/allocator.h" -#include - -namespace infini -{ - Allocator::Allocator(Runtime runtime) : runtime(runtime) - { - used = 0; - peak = 0; - ptr = nullptr; - - // 'alignment' defaults to sizeof(uint64_t), because it is the length of - // the longest data type currently supported by the DataType field of - // the tensor - alignment = sizeof(uint64_t); - } - - Allocator::~Allocator() - { - if (this->ptr != nullptr) - { - runtime->dealloc(this->ptr); - } - } - - size_t Allocator::alloc(size_t size) - { - IT_ASSERT(this->ptr == nullptr); - // pad the size to the multiple of alignment - size = this->getAlignedSize(size); - - // =================================== 作业 =================================== - // TODO: 设计一个算法来分配内存,返回起始地址偏移量 - // =================================== 作业 =================================== - - return 0; - } - - void Allocator::free(size_t addr, size_t size) - { - IT_ASSERT(this->ptr == nullptr); - size = getAlignedSize(size); - - // =================================== 作业 =================================== - // TODO: 设计一个算法来回收内存 - // =================================== 作业 =================================== - } - - void *Allocator::getPtr() - { - if (this->ptr == nullptr) - { - this->ptr = runtime->alloc(this->peak); - printf("Allocator really alloc: %p %lu bytes\n", this->ptr, peak); - } - return this->ptr; - } - - size_t Allocator::getAlignedSize(size_t size) - { - return ((size - 1) / this->alignment + 1) * this->alignment; - } - - void Allocator::info() - { - std::cout << "Used memory: " << this->used - << ", peak memory: " << this->peak << std::endl; - } -} +#include "core/allocator.h" +#include + +namespace infini +{ + Allocator::Allocator(Runtime runtime) : runtime(runtime) + { + used = 0; + peak = 0; + ptr = nullptr; + + // 'alignment' defaults to sizeof(uint64_t), because it is the length of + // the longest data type currently supported by the DataType field of + // the tensor + alignment = sizeof(uint64_t); + } + + Allocator::~Allocator() + { + if (this->ptr != nullptr) + { + runtime->dealloc(this->ptr); + } + } + + size_t Allocator::alloc(size_t size) + { + IT_ASSERT(this->ptr == nullptr); + // pad the size to the multiple of alignment + size = this->getAlignedSize(size); + + // =================================== 作业 =================================== + // TODO: 设计一个算法来分配内存,返回起始地址偏移量 + // =================================== 作业 =================================== + // First-fit: search free blocks for a suitable one + for (auto it = freeBlocks.begin(); it != freeBlocks.end(); ++it) { + if (it->second >= size) { + size_t addr = it->first; + size_t remaining = it->second - size; + freeBlocks.erase(it); + if (remaining > 0) { + freeBlocks[addr + size] = remaining; + } + return addr; + } + } + // No suitable free block found, allocate at the end + size_t addr = used; + used += size; + peak = std::max(peak, used); + return addr; + } + + void Allocator::free(size_t addr, size_t size) + { + IT_ASSERT(this->ptr == nullptr); + size = getAlignedSize(size); + + // =================================== 作业 =================================== + // TODO: 设计一个算法来回收内存 + // =================================== 作业 =================================== + // If the block is at the end of used memory, shrink used + if (addr + size == used) { + used = addr; + // Remove any trailing free blocks that are now past the new used + while (!freeBlocks.empty()) { + auto last = std::prev(freeBlocks.end()); + if (last->first + last->second == addr) { + addr = last->first; + size += last->second; + freeBlocks.erase(last); + } else { + break; + } + } + used = addr; + return; + } + + // Insert into free blocks + auto [it, inserted] = freeBlocks.insert({addr, size}); + IT_ASSERT(inserted); + + // Merge with next block if adjacent + auto next = std::next(it); + if (next != freeBlocks.end() && addr + size == next->first) { + it->second += next->second; + freeBlocks.erase(next); + } + + // Merge with previous block if adjacent + if (it != freeBlocks.begin()) { + auto prev = std::prev(it); + if (prev->first + prev->second == addr) { + prev->second += it->second; + freeBlocks.erase(it); + } + } + } + + void *Allocator::getPtr() + { + if (this->ptr == nullptr) + { + this->ptr = runtime->alloc(this->peak); + printf("Allocator really alloc: %p %lu bytes\n", this->ptr, peak); + } + return this->ptr; + } + + size_t Allocator::getAlignedSize(size_t size) + { + return ((size - 1) / this->alignment + 1) * this->alignment; + } + + void Allocator::info() + { + std::cout << "Used memory: " << this->used + << ", peak memory: " << this->peak << std::endl; + } +} diff --git a/src/core/data_type.cc b/src/core/data_type.cc index 3825c9cc..ccfdee5a 100644 --- a/src/core/data_type.cc +++ b/src/core/data_type.cc @@ -1,22 +1,22 @@ -#include "core/data_type.h" - -namespace infini { -// Move implementation here to avoid compile time error on some platform -// to be consistent with onnx -// https://github.com/onnx/onnx/blob/aeb21329122b96df1d3ef33b500a35ca140b1431/onnx/onnx.proto#L484 -const DataType DataType::Undefine(0); -const DataType DataType::Float32(1); -const DataType DataType::UInt8(2); -const DataType DataType::Int8(3); -const DataType DataType::UInt16(4); -const DataType DataType::Int16(5); -const DataType DataType::Int32(6); -const DataType DataType::Int64(7); -const DataType DataType::String(8); -const DataType DataType::Bool(9); -const DataType DataType::Float16(10); -const DataType DataType::Double(11); -const DataType DataType::UInt32(12); -const DataType DataType::UInt64(13); -const DataType DataType::BFloat16(16); -} // namespace infini +#include "core/data_type.h" + +namespace infini { +// Move implementation here to avoid compile time error on some platform +// to be consistent with onnx +// https://github.com/onnx/onnx/blob/aeb21329122b96df1d3ef33b500a35ca140b1431/onnx/onnx.proto#L484 +const DataType DataType::Undefine(0); +const DataType DataType::Float32(1); +const DataType DataType::UInt8(2); +const DataType DataType::Int8(3); +const DataType DataType::UInt16(4); +const DataType DataType::Int16(5); +const DataType DataType::Int32(6); +const DataType DataType::Int64(7); +const DataType DataType::String(8); +const DataType DataType::Bool(9); +const DataType DataType::Float16(10); +const DataType DataType::Double(11); +const DataType DataType::UInt32(12); +const DataType DataType::UInt64(13); +const DataType DataType::BFloat16(16); +} // namespace infini diff --git a/src/core/graph.cc b/src/core/graph.cc index 3a906370..b014aa43 100644 --- a/src/core/graph.cc +++ b/src/core/graph.cc @@ -1,230 +1,448 @@ -#include "core/graph.h" -#include -#include -#include - -namespace infini -{ - - void GraphObj::addOperatorAndConnect(const Operator &op) - { - sorted = false; - ops.push_back(op); - for (auto &input : op->getInputs()) - { - if (input) - { - input->addTarget(op); - if (auto pred = input->getSource()) - { - pred->addSuccessors(op); - op->addPredecessors(pred); - } - } - } - for (auto &output : op->getOutputs()) - { - if (output) - { - output->setSource(op); - for (auto &succ : output->getTargets()) - { - succ->addPredecessors(op); - op->addSuccessors(succ); - } - } - } - } - - string GraphObj::toString() const - { - std::ostringstream oss; - oss << "Graph Tensors:\n"; - for (const auto &tensor : tensors) - oss << tensor << "\n"; - - oss << "Graph operators:\n"; - for (const auto &op : ops) - { - vector preds, succs; - for (auto &o : op->getPredecessors()) - preds.emplace_back(o->getGuid()); - for (auto &o : op->getSuccessors()) - succs.emplace_back(o->getGuid()); - oss << "OP " << op->getGuid(); - oss << ", pred " << vecToString(preds); - oss << ", succ " << vecToString(succs); - oss << ", " << op << "\n"; - } - return oss.str(); - } - - bool GraphObj::topo_sort() - { - if (this->sorted) - { - return true; - } - std::vector sorted; - std::unordered_set flags; - sorted.reserve(ops.size()); - flags.reserve(ops.size()); - while (sorted.size() < ops.size()) - { - // Any node is move to sorted in this loop. - auto modified = false; - for (auto const &op : ops) - { - if (auto const &inputs = op->getInputs(); - flags.find(op.get()) == flags.end() && - std::all_of(inputs.begin(), inputs.end(), - [&flags](auto const &input) - { - auto ptr = input->getSource().get(); - return !ptr || flags.find(ptr) != flags.end(); - })) - { - modified = true; - sorted.emplace_back(op); - flags.insert(op.get()); - } - } - if (!modified) - { - return false; - } - } - this->ops = std::move(sorted); - return this->sorted = true; - } - - void GraphObj::optimize() - { - // =================================== 作业 =================================== - // TODO: 设计一个算法来实现指定的图优化规则 - // 图优化规则如下: - // 1. 去除冗余的算子(例如,两个相邻的算子都是 transpose 算子,且做的是相反的操作,可以将其全部删除) - // 2. 合并算子(例如,矩阵乘算子中含有属性transA、transB,如果其输入存在transpose,且对最后两个维度做交换,就可以将transpose融入到矩阵乘算子的属性中去) - // =================================== 作业 =================================== - } - - Tensor GraphObj::getTensor(int fuid) const - { - for (auto tensor : tensors) - { - if (tensor->getFuid() == fuid) - { - return tensor; - } - } - return nullptr; - } - - void GraphObj::shape_infer() - { - for (auto &op : ops) - { - auto ans = op->inferShape(); - IT_ASSERT(ans.has_value()); - auto oldOutputs = op->getOutputs(); - IT_ASSERT(ans.value().size() == oldOutputs.size()); - // replace the old outputshape and size with new one - for (int i = 0; i < (int)ans.value().size(); ++i) - { - auto newShape = ans.value()[i]; - auto oldShape = oldOutputs[i]->getDims(); - auto fuid = oldOutputs[i]->getFuid(); - if (newShape != oldShape) - { - auto tensor = this->getTensor(fuid); - tensor->setShape(newShape); - } - } - } - } - - void GraphObj::dataMalloc() - { - // topological sorting first - IT_ASSERT(topo_sort() == true); - - // =================================== 作业 =================================== - // TODO:利用 allocator 给计算图分配内存 - // HINT: 获取分配好的内存指针后,可以调用 tensor 的 setDataBlob 函数给 tensor 绑定内存 - // =================================== 作业 =================================== - - allocator.info(); - } - - Tensor GraphObj::addTensor(Shape dim, DataType dtype) - { - return tensors.emplace_back(make_ref(dim, dtype, runtime)); - } - - Tensor GraphObj::addTensor(const Tensor &tensor) - { - IT_ASSERT(tensor->getRuntime() == runtime, - std::string("Tensor runtime mismatch: cannot add a tenosr in ") + - tensor->getRuntime()->toString() + " to " + - runtime->toString()); - tensors.emplace_back(tensor); - return tensor; - } - - TensorVec GraphObj::addTensor(const TensorVec &tensors) - { - for (auto &t : tensors) - addTensor(t); - return tensors; - } - - // tensor's "source" and "target" must be in "ops". - // tensor has no "source" and no "target" must not exist. - // "inputs" or "outputs" of operators must be in "tensors" - // "predecessors" and "successors" of an operator of "ops" must be in "ops". - bool GraphObj::checkValid() const - { - for (auto tensor : tensors) - { - IT_ASSERT(!(tensor->getTargets().size() == 0 && - nullptr == tensor->getSource())); - for (auto op : tensor->getTargets()) - { - IT_ASSERT(std::find(ops.begin(), ops.end(), op) != ops.end()); - } - auto op = tensor->getSource(); - IT_ASSERT(!(op && std::find(ops.begin(), ops.end(), op) == ops.end())); - } - for (auto op : ops) - { - for (auto tensor : op->getInputs()) - { - IT_ASSERT(std::find(tensors.begin(), tensors.end(), tensor) != - tensors.end()); - } - for (auto tensor : op->getOutputs()) - { - IT_ASSERT(std::find(tensors.begin(), tensors.end(), tensor) != - tensors.end()); - } - for (auto pre : op->getPredecessors()) - { - IT_ASSERT(std::find(ops.begin(), ops.end(), pre) != ops.end()); - } - for (auto suc : op->getSuccessors()) - { - IT_ASSERT(std::find(ops.begin(), ops.end(), suc) != ops.end()); - } - } - std::set s; - // check whether two tensors with the same FUID exist - for (auto tensor : tensors) - { - int cnt = s.count(tensor->getFuid()); - IT_ASSERT(cnt == 0, std::to_string(tensor->getFuid())); - s.insert(tensor->getFuid()); - } - return true; - } - +#include "core/graph.h" +#include "operators/matmul.h" +#include "operators/transpose.h" +#include +#include +#include + +namespace infini +{ + + void GraphObj::addOperatorAndConnect(const Operator &op) + { + sorted = false; + ops.push_back(op); + for (auto &input : op->getInputs()) + { + if (input) + { + input->addTarget(op); + if (auto pred = input->getSource()) + { + pred->addSuccessors(op); + op->addPredecessors(pred); + } + } + } + for (auto &output : op->getOutputs()) + { + if (output) + { + output->setSource(op); + for (auto &succ : output->getTargets()) + { + succ->addPredecessors(op); + op->addSuccessors(succ); + } + } + } + } + + string GraphObj::toString() const + { + std::ostringstream oss; + oss << "Graph Tensors:\n"; + for (const auto &tensor : tensors) + oss << tensor << "\n"; + + oss << "Graph operators:\n"; + for (const auto &op : ops) + { + vector preds, succs; + for (auto &o : op->getPredecessors()) + preds.emplace_back(o->getGuid()); + for (auto &o : op->getSuccessors()) + succs.emplace_back(o->getGuid()); + oss << "OP " << op->getGuid(); + oss << ", pred " << vecToString(preds); + oss << ", succ " << vecToString(succs); + oss << ", " << op << "\n"; + } + return oss.str(); + } + + bool GraphObj::topo_sort() + { + if (this->sorted) + { + return true; + } + std::vector sorted; + std::unordered_set flags; + sorted.reserve(ops.size()); + flags.reserve(ops.size()); + while (sorted.size() < ops.size()) + { + // Any node is move to sorted in this loop. + auto modified = false; + for (auto const &op : ops) + { + if (auto const &inputs = op->getInputs(); + flags.find(op.get()) == flags.end() && + std::all_of(inputs.begin(), inputs.end(), + [&flags](auto const &input) + { + auto ptr = input->getSource().get(); + return !ptr || flags.find(ptr) != flags.end(); + })) + { + modified = true; + sorted.emplace_back(op); + flags.insert(op.get()); + } + } + if (!modified) + { + return false; + } + } + this->ops = std::move(sorted); + return this->sorted = true; + } + + void GraphObj::optimize() + { + // =================================== 作业 =================================== + // TODO: 设计一个算法来实现指定的图优化规则 + // 图优化规则如下: + // 1. 去除冗余的算子(例如,两个相邻的算子都是 transpose 算子,且做的是相反的操作,可以将其全部删除) + // 2. 合并算子(例如,矩阵乘算子中含有属性transA、transB,如果其输入存在transpose,且对最后两个维度做交换,就可以将transpose融入到矩阵乘算子的属性中去) + // =================================== 作业 =================================== + + bool changed = true; + while (changed) { + changed = false; + + // Rule 1: Remove redundant consecutive transposes + for (auto it = ops.begin(); it != ops.end(); ) { + auto op = *it; + if (op->getOpType() != OpType::Transpose) { + ++it; + continue; + } + auto transpose1 = as(op); + auto output = transpose1->getOutput(); + auto targets = output->getTargets(); + if (targets.size() != 1) { + ++it; + continue; + } + auto nextOp = targets[0]; + if (nextOp->getOpType() != OpType::Transpose) { + ++it; + continue; + } + auto transpose2 = as(nextOp); + const auto &perm1 = transpose1->getPermute(); + const auto &perm2 = transpose2->getPermute(); + // Check if perm1 and perm2 are inverses + bool areInverses = true; + for (size_t i = 0; i < perm1.size(); ++i) { + if (perm2[perm1[i]] != (int)i) { + areInverses = false; + break; + } + } + if (!areInverses) { + ++it; + continue; + } + // Remove both transposes: connect input of transpose1 to consumers of transpose2 + auto input = transpose1->getInputs(0); + auto output2 = transpose2->getOutput(); + + // Replace output2 with input in all consumers of output2 + auto consumers = output2->getTargets(); + for (auto &consumer : consumers) { + consumer->replaceInput(output2, input); + input->addTarget(consumer); + // Update predecessor/successor: transpose2 is removed, so consumer loses it as predecessor + consumer->removePredecessors(transpose2); + // If input has a source, it becomes a new predecessor of consumer + if (auto inputSource = input->getSource()) { + inputSource->addSuccessors(consumer); + consumer->addPredecessors(inputSource); + } + } + input->removeTarget(transpose1); + + // Clean up the graph connections for the removed operators + transpose1->removeSuccessors(transpose2); + transpose2->removePredecessors(transpose1); + + // Remove tensors + output->setSource(nullptr); + output->removeTarget(transpose2); + output2->setSource(nullptr); + for (auto &t : output2->getTargets()) + output2->removeTarget(t); + removeTensor(output); + removeTensor(output2); + + // Remove both ops + it = ops.erase(it); + for (auto it2 = ops.begin(); it2 != ops.end(); ++it2) { + if (*it2 == nextOp) { + ops.erase(it2); + break; + } + } + changed = true; + break; // Restart iteration after modification + } + if (changed) + continue; + + // Rule 2: Fuse transpose into matmul + for (auto it = ops.begin(); it != ops.end(); ) { + auto op = *it; + if (op->getOpType() != OpType::Transpose) { + ++it; + continue; + } + auto transpose = as(op); + auto output = transpose->getOutput(); + auto targets = output->getTargets(); + if (targets.size() != 1) { + ++it; + continue; + } + auto consumer = targets[0]; + if (consumer->getOpType() != OpType::MatMul) { + ++it; + continue; + } + const auto &perm = transpose->getPermute(); + int rank = perm.size(); + // Check if transpose only swaps the last two dimensions + bool swapsLastTwo = true; + for (int i = 0; i < rank - 2; ++i) { + if (perm[i] != i) { + swapsLastTwo = false; + break; + } + } + if (!swapsLastTwo || perm[rank - 2] != rank - 1 || perm[rank - 1] != rank - 2) { + ++it; + continue; + } + // Fuse into matmul + auto matmul = as(consumer); + auto input = transpose->getInputs(0); + if (output == matmul->getInputs(0)) { + matmul->setTransA(!matmul->getTransA()); + } else if (output == matmul->getInputs(1)) { + matmul->setTransB(!matmul->getTransB()); + } + // Replace output with input in matmul + matmul->replaceInput(output, input); + input->removeTarget(transpose); + input->addTarget(matmul); + + // Update predecessor/successor: transpose is removed, so matmul loses it as predecessor + matmul->removePredecessors(transpose); + transpose->removeSuccessors(matmul); + // If input has a source, it becomes a new predecessor of matmul + if (auto inputSource = input->getSource()) { + inputSource->addSuccessors(matmul); + matmul->addPredecessors(inputSource); + } + + // Remove transpose and output tensor + output->setSource(nullptr); + for (auto &t : output->getTargets()) + output->removeTarget(t); + removeTensor(output); + it = ops.erase(it); + changed = true; + break; + } + } + } + + Tensor GraphObj::getTensor(int fuid) const + { + for (auto tensor : tensors) + { + if (tensor->getFuid() == fuid) + { + return tensor; + } + } + return nullptr; + } + + void GraphObj::shape_infer() + { + for (auto &op : ops) + { + auto ans = op->inferShape(); + IT_ASSERT(ans.has_value()); + auto oldOutputs = op->getOutputs(); + IT_ASSERT(ans.value().size() == oldOutputs.size()); + // replace the old outputshape and size with new one + for (int i = 0; i < (int)ans.value().size(); ++i) + { + auto newShape = ans.value()[i]; + auto oldShape = oldOutputs[i]->getDims(); + auto fuid = oldOutputs[i]->getFuid(); + if (newShape != oldShape) + { + auto tensor = this->getTensor(fuid); + tensor->setShape(newShape); + } + } + } + } + + void GraphObj::dataMalloc() + { + // topological sorting first + IT_ASSERT(topo_sort() == true); + + // =================================== 作业 =================================== + // TODO:利用 allocator 给计算图分配内存 + // HINT: 获取分配好的内存指针后,可以调用 tensor 的 setDataBlob 函数给 tensor 绑定内存 + // =================================== 作业 =================================== + + // Track tensor liveness: for each tensor, find the index of its last consumer + std::unordered_map lastConsumerIdx; + std::unordered_map consumerCount; + for (auto &tensor : tensors) { + consumerCount[tensor->getGuid()] = tensor->getTargets().size(); + lastConsumerIdx[tensor->getGuid()] = -1; + } + + // Find the index of each operator in topological order + std::unordered_map opIdx; + for (int i = 0; i < (int)ops.size(); ++i) { + opIdx[ops[i]->getGuid()] = i; + } + + // For each tensor, find the last consumer index + for (auto &tensor : tensors) { + for (auto &target : tensor->getTargets()) { + int idx = opIdx[target->getGuid()]; + lastConsumerIdx[tensor->getGuid()] = std::max(lastConsumerIdx[tensor->getGuid()], idx); + } + // If tensor has no consumers (graph output), keep it alive + if (tensor->getTargets().empty()) { + if (auto src = tensor->getSource()) { + lastConsumerIdx[tensor->getGuid()] = opIdx[src->getGuid()]; + } + } + } + + // Allocate memory for each tensor, freeing when no longer needed + std::unordered_map tensorOffsets; + + // First, allocate graph input tensors (tensors without a source) + for (auto &tensor : tensors) { + if (!tensor->getSource() && tensor->getTargets().size() > 0) { + size_t offset = allocator.alloc(tensor->getBytes()); + tensorOffsets[tensor->getGuid()] = offset; + } + } + + for (int i = 0; i < (int)ops.size(); ++i) { + auto &op = ops[i]; + // Allocate output tensors + for (auto &output : op->getOutputs()) { + size_t offset = allocator.alloc(output->getBytes()); + tensorOffsets[output->getGuid()] = offset; + } + // Free input tensors that are no longer needed + for (auto &input : op->getInputs()) { + if (input) { + int remaining = --consumerCount[input->getGuid()]; + if (remaining == 0 && lastConsumerIdx[input->getGuid()] == i) { + allocator.free(tensorOffsets[input->getGuid()], input->getBytes()); + } + } + } + } + + // Get the actual memory pointer and assign Blobs to tensors + void *basePtr = allocator.getPtr(); + for (auto &tensor : tensors) { + if (tensorOffsets.count(tensor->getGuid())) { + auto blob = make_ref(runtime, (char *)basePtr + tensorOffsets[tensor->getGuid()]); + tensor->setDataBlob(blob); + } + } + + allocator.info(); + } + + Tensor GraphObj::addTensor(Shape dim, DataType dtype) + { + return tensors.emplace_back(make_ref(dim, dtype, runtime)); + } + + Tensor GraphObj::addTensor(const Tensor &tensor) + { + IT_ASSERT(tensor->getRuntime() == runtime, + std::string("Tensor runtime mismatch: cannot add a tenosr in ") + + tensor->getRuntime()->toString() + " to " + + runtime->toString()); + tensors.emplace_back(tensor); + return tensor; + } + + TensorVec GraphObj::addTensor(const TensorVec &tensors) + { + for (auto &t : tensors) + addTensor(t); + return tensors; + } + + // tensor's "source" and "target" must be in "ops". + // tensor has no "source" and no "target" must not exist. + // "inputs" or "outputs" of operators must be in "tensors" + // "predecessors" and "successors" of an operator of "ops" must be in "ops". + bool GraphObj::checkValid() const + { + for (auto tensor : tensors) + { + IT_ASSERT(!(tensor->getTargets().size() == 0 && + nullptr == tensor->getSource())); + for (auto op : tensor->getTargets()) + { + IT_ASSERT(std::find(ops.begin(), ops.end(), op) != ops.end()); + } + auto op = tensor->getSource(); + IT_ASSERT(!(op && std::find(ops.begin(), ops.end(), op) == ops.end())); + } + for (auto op : ops) + { + for (auto tensor : op->getInputs()) + { + IT_ASSERT(std::find(tensors.begin(), tensors.end(), tensor) != + tensors.end()); + } + for (auto tensor : op->getOutputs()) + { + IT_ASSERT(std::find(tensors.begin(), tensors.end(), tensor) != + tensors.end()); + } + for (auto pre : op->getPredecessors()) + { + IT_ASSERT(std::find(ops.begin(), ops.end(), pre) != ops.end()); + } + for (auto suc : op->getSuccessors()) + { + IT_ASSERT(std::find(ops.begin(), ops.end(), suc) != ops.end()); + } + } + std::set s; + // check whether two tensors with the same FUID exist + for (auto tensor : tensors) + { + int cnt = s.count(tensor->getFuid()); + IT_ASSERT(cnt == 0, std::to_string(tensor->getFuid())); + s.insert(tensor->getFuid()); + } + return true; + } + } // namespace infini \ No newline at end of file diff --git a/src/core/op_type.cc b/src/core/op_type.cc index b2a721a3..b2af61b2 100644 --- a/src/core/op_type.cc +++ b/src/core/op_type.cc @@ -1,32 +1,32 @@ -#include "core/op_type.h" - -namespace infini -{ - const char *OpType::toString() const - { -#define CASE(NAME) \ - case OpType::NAME: \ - return #NAME - - switch (type) - { - CASE(Unknown); - CASE(Add); - CASE(Sub); - CASE(Mul); - CASE(Div); - CASE(Cast); - CASE(Clip); - CASE(Relu); - CASE(Transpose); - CASE(Concat); - CASE(MatMul); - - default: - return "Unknown"; - } - -#undef CASE - } - -} // namespace infini +#include "core/op_type.h" + +namespace infini +{ + const char *OpType::toString() const + { +#define CASE(NAME) \ + case OpType::NAME: \ + return #NAME + + switch (type) + { + CASE(Unknown); + CASE(Add); + CASE(Sub); + CASE(Mul); + CASE(Div); + CASE(Cast); + CASE(Clip); + CASE(Relu); + CASE(Transpose); + CASE(Concat); + CASE(MatMul); + + default: + return "Unknown"; + } + +#undef CASE + } + +} // namespace infini diff --git a/src/core/operator.cc b/src/core/operator.cc index a70ca48c..d3622fb2 100644 --- a/src/core/operator.cc +++ b/src/core/operator.cc @@ -1,85 +1,85 @@ -#include "core/operator.h" -#include "core/graph.h" - -namespace infini -{ - - OperatorObj::OperatorObj(OpType opType, TensorVec inputs, TensorVec outputs) - : type(opType), inputs(inputs), outputs(outputs) {} - - void OperatorObj::removePredecessors(const Operator &op) - { - for (auto it = predecessors.begin(); it != predecessors.end();) - { - if (it->lock() == op) - it = predecessors.erase(it); - else - ++it; - } - } - - void OperatorObj::removeSuccessors(const Operator &op) - { - for (auto it = successors.begin(); it != successors.end();) - { - if (it->lock() == op) - it = successors.erase(it); - else - ++it; - } - } - - void OperatorObj::replaceInput(Tensor t1, Tensor t2) - { - for (auto itr = inputs.begin(); itr != inputs.end(); ++itr) - { - if (*itr == t1) - { - *itr = t2; - } - } - } - - bool OperatorObj::checkValid(GraphObj *graph) - { - auto optShapes = inferShape(); - if (!optShapes) // shape inference failed - return false; - - const vector &shapes = *optShapes; - if (shapes.size() != outputs.size()) - return false; - if (graph) - { // if graph != nullptr, outputs should be created - auto dataTypes = inferDataType(); - for (size_t i = 0; i < outputs.size(); i++) - { - IT_ASSERT(!outputs[i], "Find empty output while operator creation"); - outputs[i] = graph->addTensor(shapes[i], dataTypes[i]); - } - } - else - { // if outputs have been created, check their shapes - for (size_t i = 0; i < shapes.size(); ++i) - { - if (shapes[i] != outputs[i]->getDims()) - return false; - } - } - return true; - } - - optional> OperatorObj::inferShape() { return inferShape(inputs); } - - vector OperatorObj::inferDataType(const TensorVec &inputs) const - { - auto dataType = inputs[0]->getDType(); - return vector(numOutputs(), dataType); - } - - vector OperatorObj::inferDataType() const - { - return inferDataType(inputs); - } - -} // namespace infini +#include "core/operator.h" +#include "core/graph.h" + +namespace infini +{ + + OperatorObj::OperatorObj(OpType opType, TensorVec inputs, TensorVec outputs) + : type(opType), inputs(inputs), outputs(outputs) {} + + void OperatorObj::removePredecessors(const Operator &op) + { + for (auto it = predecessors.begin(); it != predecessors.end();) + { + if (it->lock() == op) + it = predecessors.erase(it); + else + ++it; + } + } + + void OperatorObj::removeSuccessors(const Operator &op) + { + for (auto it = successors.begin(); it != successors.end();) + { + if (it->lock() == op) + it = successors.erase(it); + else + ++it; + } + } + + void OperatorObj::replaceInput(Tensor t1, Tensor t2) + { + for (auto itr = inputs.begin(); itr != inputs.end(); ++itr) + { + if (*itr == t1) + { + *itr = t2; + } + } + } + + bool OperatorObj::checkValid(GraphObj *graph) + { + auto optShapes = inferShape(); + if (!optShapes) // shape inference failed + return false; + + const vector &shapes = *optShapes; + if (shapes.size() != outputs.size()) + return false; + if (graph) + { // if graph != nullptr, outputs should be created + auto dataTypes = inferDataType(); + for (size_t i = 0; i < outputs.size(); i++) + { + IT_ASSERT(!outputs[i], "Find empty output while operator creation"); + outputs[i] = graph->addTensor(shapes[i], dataTypes[i]); + } + } + else + { // if outputs have been created, check their shapes + for (size_t i = 0; i < shapes.size(); ++i) + { + if (shapes[i] != outputs[i]->getDims()) + return false; + } + } + return true; + } + + optional> OperatorObj::inferShape() { return inferShape(inputs); } + + vector OperatorObj::inferDataType(const TensorVec &inputs) const + { + auto dataType = inputs[0]->getDType(); + return vector(numOutputs(), dataType); + } + + vector OperatorObj::inferDataType() const + { + return inferDataType(inputs); + } + +} // namespace infini diff --git a/src/core/runtime.cc b/src/core/runtime.cc index bd88d904..09692612 100644 --- a/src/core/runtime.cc +++ b/src/core/runtime.cc @@ -1,36 +1,36 @@ -#include "core/runtime.h" -#include "core/blob.h" -#include "core/kernel.h" -#include "core/graph.h" -#include "core/kernel.h" -#include -#include -#include -namespace infini -{ - void NativeCpuRuntimeObj::run(const Graph &graph) const - { - const auto &kernelRegistry = KernelRegistry::getInstance(); - - for (auto &op : graph->getOperators()) - { - auto kernelAttrs = KernelAttrs{device, op->getOpType().underlying()}; - Kernel *kernel = kernelRegistry.getKernel(kernelAttrs); - kernel->compute(op, this); - } - } - - string NativeCpuRuntimeObj::toString() const { return "CPU Runtime"; } - - void NativeCpuRuntimeObj::dealloc(void *ptr) - { - return free(ptr); - } - - void *NativeCpuRuntimeObj::alloc(size_t size) - { - return calloc((size + sizeof(uint64_t) - 1) / sizeof(uint64_t), - sizeof(uint64_t)); - } - -} // namespace infini +#include "core/runtime.h" +#include "core/blob.h" +#include "core/kernel.h" +#include "core/graph.h" +#include "core/kernel.h" +#include +#include +#include +namespace infini +{ + void NativeCpuRuntimeObj::run(const Graph &graph) const + { + const auto &kernelRegistry = KernelRegistry::getInstance(); + + for (auto &op : graph->getOperators()) + { + auto kernelAttrs = KernelAttrs{device, op->getOpType().underlying()}; + Kernel *kernel = kernelRegistry.getKernel(kernelAttrs); + kernel->compute(op, this); + } + } + + string NativeCpuRuntimeObj::toString() const { return "CPU Runtime"; } + + void NativeCpuRuntimeObj::dealloc(void *ptr) + { + return free(ptr); + } + + void *NativeCpuRuntimeObj::alloc(size_t size) + { + return calloc((size + sizeof(uint64_t) - 1) / sizeof(uint64_t), + sizeof(uint64_t)); + } + +} // namespace infini diff --git a/src/core/tensor.cc b/src/core/tensor.cc index db54a2d6..6c11869a 100644 --- a/src/core/tensor.cc +++ b/src/core/tensor.cc @@ -1,116 +1,116 @@ -#include "core/tensor.h" -#include "core/blob.h" -#include "core/operator.h" -#include "core/runtime.h" -#include -#include - -namespace infini { - - TensorObj::TensorObj(Shape shape_, DataType dtype, Runtime runtime) - : dim(shape_.size()), dtype(dtype), runtime(runtime), shape(std::move(shape_)), - _size(std::accumulate(shape.begin(), shape.end(), 1, std::multiplies{})) {} - - string TensorObj::toString() const - { - // Convert data pointer to string - std::stringstream ss; - if (data != nullptr) - ss << data->getPtr(); - else - ss << "nullptr data"; - string ret = "Tensor " + std::to_string(guid) + ", Fuid " + - std::to_string(fuid) + ", shape " + vecToString(shape) + - ", dtype " + dtype.toString() + ", " + runtime->toString() + - ", " + ss.str() + "\n"; - vector targetGuids; - for (const auto &op : targets) - targetGuids.emplace_back(op.lock()->getGuid()); - if (auto o = source.lock()) - ret += ", source " + std::to_string(o->getGuid()); - else - ret += ", source None"; - ret += ", targets " + vecToString(targetGuids); - return ret; - } - -void TensorObj::setShape(Shape shape_) { - shape = shape_; - size_t size = std::accumulate(shape.begin(), shape.end(), 1, - [](auto acc, auto x) { return acc * x; }); - _size = size; -} - -void TensorObj::printData() const { - IT_ASSERT(data != nullptr); - if (!runtime->isCpu()) - IT_TODO_HALT(); - -#define TRY_PRINT(N) \ - if (dtype == DataType(N)) \ - std::cout << dataToString::t>() << std::endl; - - TRY_PRINT(0) // fmt: new line - else TRY_PRINT(1) // - else TRY_PRINT(2) // - else TRY_PRINT(3) // - else TRY_PRINT(4) // - else TRY_PRINT(5) // - else TRY_PRINT(6) // - else TRY_PRINT(7) // - else TRY_PRINT(8) // - else TRY_PRINT(9) // - else TRY_PRINT(10) // - else TRY_PRINT(11) // - else TRY_PRINT(12) // - else TRY_PRINT(13) // - else TRY_PRINT(16) // - else IT_TODO_HALT(); - -#undef TRY_PRINT -} - -bool TensorObj::equalData(const Tensor &rhs, double relativeError) const { - IT_ASSERT(data != nullptr); - IT_ASSERT(rhs->data != nullptr); - IT_ASSERT(getDType() == rhs->getDType()); - IT_ASSERT(runtime->isCpu()); - IT_ASSERT(rhs->getRuntime()->isCpu()); - if (size() != rhs->size()) - return false; - -#define TEST_EQUAL(N) \ - if (dtype == DataType(N)) \ - return equalDataImpl(getRawDataPtr::t *>(), \ - rhs->getRawDataPtr::t *>(), size(), \ - relativeError); - - TEST_EQUAL(0) // fmt: new line - else TEST_EQUAL(1) // - else TEST_EQUAL(2) // - else TEST_EQUAL(3) // - else TEST_EQUAL(4) // - else TEST_EQUAL(5) // - else TEST_EQUAL(6) // - else TEST_EQUAL(7) // - else TEST_EQUAL(8) // - else TEST_EQUAL(9) // - else TEST_EQUAL(10) // - else TEST_EQUAL(11) // - else TEST_EQUAL(12) // - else TEST_EQUAL(13) // - else TEST_EQUAL(16) // - else IT_TODO_HALT(); - -#undef TEST_EQUAL -} - -void TensorObj::setData( - const std::function &generator) const { - IT_ASSERT(data != nullptr); - generator(getRawDataPtr(), size(), dtype); -} - -void TensorObj::setDataBlob(const Blob &blob) { this->data = blob; } - -}; // namespace infini +#include "core/tensor.h" +#include "core/blob.h" +#include "core/operator.h" +#include "core/runtime.h" +#include +#include + +namespace infini { + + TensorObj::TensorObj(Shape shape_, DataType dtype, Runtime runtime) + : dim(shape_.size()), dtype(dtype), runtime(runtime), shape(std::move(shape_)), + _size(std::accumulate(shape.begin(), shape.end(), 1, std::multiplies{})) {} + + string TensorObj::toString() const + { + // Convert data pointer to string + std::stringstream ss; + if (data != nullptr) + ss << data->getPtr(); + else + ss << "nullptr data"; + string ret = "Tensor " + std::to_string(guid) + ", Fuid " + + std::to_string(fuid) + ", shape " + vecToString(shape) + + ", dtype " + dtype.toString() + ", " + runtime->toString() + + ", " + ss.str() + "\n"; + vector targetGuids; + for (const auto &op : targets) + targetGuids.emplace_back(op.lock()->getGuid()); + if (auto o = source.lock()) + ret += ", source " + std::to_string(o->getGuid()); + else + ret += ", source None"; + ret += ", targets " + vecToString(targetGuids); + return ret; + } + +void TensorObj::setShape(Shape shape_) { + shape = shape_; + size_t size = std::accumulate(shape.begin(), shape.end(), 1, + [](auto acc, auto x) { return acc * x; }); + _size = size; +} + +void TensorObj::printData() const { + IT_ASSERT(data != nullptr); + if (!runtime->isCpu()) + IT_TODO_HALT(); + +#define TRY_PRINT(N) \ + if (dtype == DataType(N)) \ + std::cout << dataToString::t>() << std::endl; + + TRY_PRINT(0) // fmt: new line + else TRY_PRINT(1) // + else TRY_PRINT(2) // + else TRY_PRINT(3) // + else TRY_PRINT(4) // + else TRY_PRINT(5) // + else TRY_PRINT(6) // + else TRY_PRINT(7) // + else TRY_PRINT(8) // + else TRY_PRINT(9) // + else TRY_PRINT(10) // + else TRY_PRINT(11) // + else TRY_PRINT(12) // + else TRY_PRINT(13) // + else TRY_PRINT(16) // + else IT_TODO_HALT(); + +#undef TRY_PRINT +} + +bool TensorObj::equalData(const Tensor &rhs, double relativeError) const { + IT_ASSERT(data != nullptr); + IT_ASSERT(rhs->data != nullptr); + IT_ASSERT(getDType() == rhs->getDType()); + IT_ASSERT(runtime->isCpu()); + IT_ASSERT(rhs->getRuntime()->isCpu()); + if (size() != rhs->size()) + return false; + +#define TEST_EQUAL(N) \ + if (dtype == DataType(N)) \ + return equalDataImpl(getRawDataPtr::t *>(), \ + rhs->getRawDataPtr::t *>(), size(), \ + relativeError); + + TEST_EQUAL(0) // fmt: new line + else TEST_EQUAL(1) // + else TEST_EQUAL(2) // + else TEST_EQUAL(3) // + else TEST_EQUAL(4) // + else TEST_EQUAL(5) // + else TEST_EQUAL(6) // + else TEST_EQUAL(7) // + else TEST_EQUAL(8) // + else TEST_EQUAL(9) // + else TEST_EQUAL(10) // + else TEST_EQUAL(11) // + else TEST_EQUAL(12) // + else TEST_EQUAL(13) // + else TEST_EQUAL(16) // + else IT_TODO_HALT(); + +#undef TEST_EQUAL +} + +void TensorObj::setData( + const std::function &generator) const { + IT_ASSERT(data != nullptr); + generator(getRawDataPtr(), size(), dtype); +} + +void TensorObj::setDataBlob(const Blob &blob) { this->data = blob; } + +}; // namespace infini diff --git a/src/kernels/cpu/concat.cc b/src/kernels/cpu/concat.cc index 6e061c75..2c511281 100644 --- a/src/kernels/cpu/concat.cc +++ b/src/kernels/cpu/concat.cc @@ -1,64 +1,64 @@ -#include "operators/concat.h" -#include "core/kernel.h" - -namespace infini { - -class NaiveConcat : public CpuKernelWithoutConfig { - template - void doCompute(const Operator &_op, const RuntimeObj *context) const { - auto op = as(_op); - auto inputs = op->getInputs(), outputs = op->getOutputs(); - auto dim = op->getDim(); - auto output = outputs[0]; - std::vector iDims; - for (auto input : inputs) - iDims.emplace_back(input->getDims()); - const auto &outDim = output->getDims(); - size_t blockOffsetInner = 1; - for (size_t i = outDim.size() - 1; i > (size_t)dim; --i) - blockOffsetInner *= outDim[i]; - size_t blockOffset = outDim[dim] * blockOffsetInner; - for (size_t i = 0; i < inputs.size(); ++i) { - auto input = inputs[i]; - auto dimOffset = 0; - auto iDim = iDims[i]; - for (size_t j = 0; j < i; ++j) - dimOffset += iDims[j][dim]; - size_t localBlockOffset = 1; - for (size_t i = iDim.size() - 1; - i >= (size_t)dim && i != (size_t)-1; --i) - localBlockOffset *= iDim[i]; - auto innerOffset = blockOffsetInner * dimOffset; - auto inSize = input->size(); - auto inPtr = input->getRawDataPtr(), - outPtr = output->getRawDataPtr(); -#pragma omp parallel for - for (size_t iOffset = 0; iOffset < inSize; ++iOffset) { - auto oOffset = iOffset % localBlockOffset + innerOffset + - iOffset / localBlockOffset * blockOffset; - outPtr[oOffset] = inPtr[iOffset]; - } - } - } - - void compute(const Operator &_op, - const RuntimeObj *context) const override { -#define CASE(N) \ - case N: \ - doCompute::t>(_op, context) - - int dataTypeIdx = _op->getDType().getIndex(); - switch (dataTypeIdx) { - CASE(1); // DataType::Float32 - break; - CASE(12); // DataType::UInt32 - break; - default: - IT_TODO_HALT(); - } - } -}; - -REGISTER_KERNEL(Device::CPU, OpType::Concat, NaiveConcat, "ConcatNaive_CPU"); - -} // namespace infini +#include "operators/concat.h" +#include "core/kernel.h" + +namespace infini { + +class NaiveConcat : public CpuKernelWithoutConfig { + template + void doCompute(const Operator &_op, const RuntimeObj *context) const { + auto op = as(_op); + auto inputs = op->getInputs(), outputs = op->getOutputs(); + auto dim = op->getDim(); + auto output = outputs[0]; + std::vector iDims; + for (auto input : inputs) + iDims.emplace_back(input->getDims()); + const auto &outDim = output->getDims(); + size_t blockOffsetInner = 1; + for (size_t i = outDim.size() - 1; i > (size_t)dim; --i) + blockOffsetInner *= outDim[i]; + size_t blockOffset = outDim[dim] * blockOffsetInner; + for (size_t i = 0; i < inputs.size(); ++i) { + auto input = inputs[i]; + auto dimOffset = 0; + auto iDim = iDims[i]; + for (size_t j = 0; j < i; ++j) + dimOffset += iDims[j][dim]; + size_t localBlockOffset = 1; + for (size_t i = iDim.size() - 1; + i >= (size_t)dim && i != (size_t)-1; --i) + localBlockOffset *= iDim[i]; + auto innerOffset = blockOffsetInner * dimOffset; + auto inSize = input->size(); + auto inPtr = input->getRawDataPtr(), + outPtr = output->getRawDataPtr(); +#pragma omp parallel for + for (size_t iOffset = 0; iOffset < inSize; ++iOffset) { + auto oOffset = iOffset % localBlockOffset + innerOffset + + iOffset / localBlockOffset * blockOffset; + outPtr[oOffset] = inPtr[iOffset]; + } + } + } + + void compute(const Operator &_op, + const RuntimeObj *context) const override { +#define CASE(N) \ + case N: \ + doCompute::t>(_op, context) + + int dataTypeIdx = _op->getDType().getIndex(); + switch (dataTypeIdx) { + CASE(1); // DataType::Float32 + break; + CASE(12); // DataType::UInt32 + break; + default: + IT_TODO_HALT(); + } + } +}; + +REGISTER_KERNEL(Device::CPU, OpType::Concat, NaiveConcat, "ConcatNaive_CPU"); + +} // namespace infini diff --git a/src/kernels/cpu/element_wise.cc b/src/kernels/cpu/element_wise.cc index af03c7a3..ba3361cd 100644 --- a/src/kernels/cpu/element_wise.cc +++ b/src/kernels/cpu/element_wise.cc @@ -1,119 +1,119 @@ -#include "operators/element_wise.h" -#include "core/kernel.h" -#include "utils/operator_utils.h" - -namespace infini -{ - class NativeElementWise : public CpuKernelWithoutConfig - { - template - static T addCompute(T val0, T val1) - { - return val0 + val1; - } - - template - static T subCompute(T val0, T val1) - { - return val0 - val1; - } - - template - static T mulCompute(T val0, T val1) - { - return val0 * val1; - } - - template - static T divCompute(T val0, T val1) - { - return (T)(val0 / val1); - } - - template - void doCompute(const Operator &_op, const RuntimeObj *context) const - { - auto op = as(_op); - T *inptr0 = op->getInputs(0)->getRawDataPtr(); - T *inptr1 = op->getInputs(1)->getRawDataPtr(); - T *outptr = op->getOutput()->getRawDataPtr(); - - auto shapeA = op->getInputs(0)->getDims(); - auto shapeB = op->getInputs(1)->getDims(); - auto shapeC = op->getOutput()->getDims(); - auto rank = op->getOutput()->getRank(); - Shape a(rank, 1); - Shape b(rank, 1); - std::copy(shapeA.begin(), shapeA.end(), - a.begin() + (rank - shapeA.size())); - std::copy(shapeB.begin(), shapeB.end(), - b.begin() + (rank - shapeB.size())); - auto getStride = [&](const Shape &shape) - { - int p = 1; - Shape stride(rank); - for (auto i = rank; i > 0; --i) - { - stride[i - 1] = p; - p = p * shape[i - 1]; - } - return stride; - }; - Shape strideA = getStride(a); - Shape strideB = getStride(b); - - auto n = op->getOutput()->size(); - T (*_doCompute) - (T val0, T val1); - switch (op->getOpType().underlying()) - { - case OpType::Add: - _doCompute = addCompute; - break; - case OpType::Sub: - _doCompute = subCompute; - break; - case OpType::Mul: - _doCompute = mulCompute; - break; - case OpType::Div: - _doCompute = divCompute; - break; - default: - IT_TODO_HALT(); - } - - for (size_t i = 0; i < n; ++i) - { - auto shapeIndexC = locate_index(i, shapeC); - auto indexA = delocate_index(shapeIndexC, a, strideA); - auto indexB = delocate_index(shapeIndexC, b, strideB); - outptr[i] = _doCompute(inptr0[indexA], inptr1[indexB]); - } - } - - void compute(const Operator &_op, - const RuntimeObj *context) const override - { -#define CASE(N) \ - case N: \ - doCompute::t>(_op, context) - - int dataTypeIdx = _op->getDType().getIndex(); - switch (dataTypeIdx) - { - CASE(1); // DataType::Float32 - break; - CASE(12); // DataType::UInt32 - break; - default: - IT_TODO_HALT(); - } - } - }; - - REGISTER_KERNEL(Device::CPU, OpType::Add, NativeElementWise, "addNaive_CPU"); - REGISTER_KERNEL(Device::CPU, OpType::Sub, NativeElementWise, "subNaive_CPU"); - REGISTER_KERNEL(Device::CPU, OpType::Mul, NativeElementWise, "mulNaive_CPU"); - REGISTER_KERNEL(Device::CPU, OpType::Div, NativeElementWise, "divNaive_CPU"); -}; // namespace infini +#include "operators/element_wise.h" +#include "core/kernel.h" +#include "utils/operator_utils.h" + +namespace infini +{ + class NativeElementWise : public CpuKernelWithoutConfig + { + template + static T addCompute(T val0, T val1) + { + return val0 + val1; + } + + template + static T subCompute(T val0, T val1) + { + return val0 - val1; + } + + template + static T mulCompute(T val0, T val1) + { + return val0 * val1; + } + + template + static T divCompute(T val0, T val1) + { + return (T)(val0 / val1); + } + + template + void doCompute(const Operator &_op, const RuntimeObj *context) const + { + auto op = as(_op); + T *inptr0 = op->getInputs(0)->getRawDataPtr(); + T *inptr1 = op->getInputs(1)->getRawDataPtr(); + T *outptr = op->getOutput()->getRawDataPtr(); + + auto shapeA = op->getInputs(0)->getDims(); + auto shapeB = op->getInputs(1)->getDims(); + auto shapeC = op->getOutput()->getDims(); + auto rank = op->getOutput()->getRank(); + Shape a(rank, 1); + Shape b(rank, 1); + std::copy(shapeA.begin(), shapeA.end(), + a.begin() + (rank - shapeA.size())); + std::copy(shapeB.begin(), shapeB.end(), + b.begin() + (rank - shapeB.size())); + auto getStride = [&](const Shape &shape) + { + int p = 1; + Shape stride(rank); + for (auto i = rank; i > 0; --i) + { + stride[i - 1] = p; + p = p * shape[i - 1]; + } + return stride; + }; + Shape strideA = getStride(a); + Shape strideB = getStride(b); + + auto n = op->getOutput()->size(); + T (*_doCompute) + (T val0, T val1); + switch (op->getOpType().underlying()) + { + case OpType::Add: + _doCompute = addCompute; + break; + case OpType::Sub: + _doCompute = subCompute; + break; + case OpType::Mul: + _doCompute = mulCompute; + break; + case OpType::Div: + _doCompute = divCompute; + break; + default: + IT_TODO_HALT(); + } + + for (size_t i = 0; i < n; ++i) + { + auto shapeIndexC = locate_index(i, shapeC); + auto indexA = delocate_index(shapeIndexC, a, strideA); + auto indexB = delocate_index(shapeIndexC, b, strideB); + outptr[i] = _doCompute(inptr0[indexA], inptr1[indexB]); + } + } + + void compute(const Operator &_op, + const RuntimeObj *context) const override + { +#define CASE(N) \ + case N: \ + doCompute::t>(_op, context) + + int dataTypeIdx = _op->getDType().getIndex(); + switch (dataTypeIdx) + { + CASE(1); // DataType::Float32 + break; + CASE(12); // DataType::UInt32 + break; + default: + IT_TODO_HALT(); + } + } + }; + + REGISTER_KERNEL(Device::CPU, OpType::Add, NativeElementWise, "addNaive_CPU"); + REGISTER_KERNEL(Device::CPU, OpType::Sub, NativeElementWise, "subNaive_CPU"); + REGISTER_KERNEL(Device::CPU, OpType::Mul, NativeElementWise, "mulNaive_CPU"); + REGISTER_KERNEL(Device::CPU, OpType::Div, NativeElementWise, "divNaive_CPU"); +}; // namespace infini diff --git a/src/kernels/cpu/transpose.cc b/src/kernels/cpu/transpose.cc index 46292d45..b43dd6ec 100644 --- a/src/kernels/cpu/transpose.cc +++ b/src/kernels/cpu/transpose.cc @@ -1,60 +1,60 @@ -#include "operators/transpose.h" -#include "core/kernel.h" - -namespace infini { - -inline Shape idx2Pos(const Shape &shape, size_t idx) { - Shape pos = Shape(shape.size(), 0); - auto rest = idx, curDimId = shape.size() - 1; - while (rest > 0) { - pos[curDimId] = rest % shape[curDimId]; - rest /= shape[curDimId]; - curDimId--; - } - return pos; -} - -class NaiveTranspose : public CpuKernelWithoutConfig { - template - void doCompute(const Operator &_op, const RuntimeObj *context) const { - auto op = as(_op); - auto inputs = op->getInputs(), outputs = op->getOutputs(); - const auto &inDim = inputs[0]->getDims(); - const auto &perm = op->getPermute(); - - size_t inSize = inputs[0]->size(); - auto inPtr = inputs[0]->getRawDataPtr(), - outPtr = outputs[0]->getRawDataPtr(); - // #pragma omp parallel for - for (size_t inIdx = 0; inIdx < inSize; ++inIdx) { - auto posInput = idx2Pos(inDim, inIdx); - int outIdx = 0; - for (size_t j = 0, jEnd = perm.size(); j < jEnd; ++j) { - outIdx = outIdx * inDim[perm[j]] + posInput[perm[j]]; - } - outPtr[outIdx] = inPtr[inIdx]; - } - } - - void compute(const Operator &_op, - const RuntimeObj *context) const override { -#define CASE(N) \ - case N: \ - doCompute::t>(_op, context) - - int dataTypeIdx = _op->getDType().getIndex(); - switch (dataTypeIdx) { - CASE(1); // DataType::Float32 - break; - CASE(12); // DataType::UInt32 - break; - default: - IT_TODO_HALT(); - } - } -}; - -REGISTER_KERNEL(Device::CPU, OpType::Transpose, NaiveTranspose, - "TransposeNaive_CPU"); - -} // namespace infini +#include "operators/transpose.h" +#include "core/kernel.h" + +namespace infini { + +inline Shape idx2Pos(const Shape &shape, size_t idx) { + Shape pos = Shape(shape.size(), 0); + auto rest = idx, curDimId = shape.size() - 1; + while (rest > 0) { + pos[curDimId] = rest % shape[curDimId]; + rest /= shape[curDimId]; + curDimId--; + } + return pos; +} + +class NaiveTranspose : public CpuKernelWithoutConfig { + template + void doCompute(const Operator &_op, const RuntimeObj *context) const { + auto op = as(_op); + auto inputs = op->getInputs(), outputs = op->getOutputs(); + const auto &inDim = inputs[0]->getDims(); + const auto &perm = op->getPermute(); + + size_t inSize = inputs[0]->size(); + auto inPtr = inputs[0]->getRawDataPtr(), + outPtr = outputs[0]->getRawDataPtr(); + // #pragma omp parallel for + for (size_t inIdx = 0; inIdx < inSize; ++inIdx) { + auto posInput = idx2Pos(inDim, inIdx); + int outIdx = 0; + for (size_t j = 0, jEnd = perm.size(); j < jEnd; ++j) { + outIdx = outIdx * inDim[perm[j]] + posInput[perm[j]]; + } + outPtr[outIdx] = inPtr[inIdx]; + } + } + + void compute(const Operator &_op, + const RuntimeObj *context) const override { +#define CASE(N) \ + case N: \ + doCompute::t>(_op, context) + + int dataTypeIdx = _op->getDType().getIndex(); + switch (dataTypeIdx) { + CASE(1); // DataType::Float32 + break; + CASE(12); // DataType::UInt32 + break; + default: + IT_TODO_HALT(); + } + } +}; + +REGISTER_KERNEL(Device::CPU, OpType::Transpose, NaiveTranspose, + "TransposeNaive_CPU"); + +} // namespace infini diff --git a/src/kernels/cpu/unary.cc b/src/kernels/cpu/unary.cc index 29f88548..9e631c5f 100644 --- a/src/kernels/cpu/unary.cc +++ b/src/kernels/cpu/unary.cc @@ -1,105 +1,105 @@ -#include "operators/unary.h" -#include "core/kernel.h" - -namespace infini -{ - class NativeUnary : public CpuKernelWithoutConfig - { - template - static T reluCompute(T val) - { - return std::max(T(0), val); - } - - template - void doCompute(const Operator &_op, const RuntimeObj *context) const - { - auto op = as(_op); - T *inptr = op->getInputs(0)->getRawDataPtr(); - T *outptr = op->getOutput()->getRawDataPtr(); - - auto outDim = op->getOutput()->getDims(); - auto n = op->getOutput()->size(); - - T (*_doCompute) - (T val); - switch (op->getOpType().underlying()) - { - case OpType::Relu: - _doCompute = reluCompute; - break; - default: - IT_TODO_HALT(); - } - - for (size_t offset = 0; offset < n; offset++) - { - outptr[offset] = _doCompute(inptr[offset]); - } - } - - void compute(const Operator &_op, - const RuntimeObj *context) const override - { -#define CASE(N) \ - case N: \ - doCompute::t>(_op, context) - - int dataTypeIdx = _op->getDType().getIndex(); - switch (dataTypeIdx) - { - CASE(1); // DataType::Float32 - break; - CASE(12); // DataType::UInt32 - break; - default: - IT_TODO_HALT(); - } - } - }; - - class Clip : public CpuKernelWithoutConfig - { - template - void doCompute(const Operator &_op, const RuntimeObj *context) const - { - auto op = as(_op); - T *inptr = op->getInputs(0)->getRawDataPtr(); - T *outptr = op->getOutput()->getRawDataPtr(); - auto minValue = op->getMin(); - auto maxValue = op->getMax(); - - auto n = op->getOutput()->size(); - for (size_t offset = 0; offset < n; offset++) - { - auto val = *inptr++; - *outptr++ = (minValue && val < *minValue) ? *minValue - : (maxValue && val > *maxValue) ? *maxValue - : val; - } - } - - void compute(const Operator &_op, - const RuntimeObj *context) const override - { -#define CASE(N) \ - case N: \ - doCompute::t>(_op, context) - - int dataTypeIdx = _op->getDType().getIndex(); - switch (dataTypeIdx) - { - CASE(1); // DataType::Float32 - break; - CASE(12); // DataType::UInt32 - break; - default: - IT_TODO_HALT(); - } - } - }; - - REGISTER_KERNEL(Device::CPU, OpType::Relu, NativeUnary, "reluNaive_CPU"); - REGISTER_KERNEL(Device::CPU, OpType::Clip, Clip, "Clip_CPU"); - -}; // namespace infini +#include "operators/unary.h" +#include "core/kernel.h" + +namespace infini +{ + class NativeUnary : public CpuKernelWithoutConfig + { + template + static T reluCompute(T val) + { + return std::max(T(0), val); + } + + template + void doCompute(const Operator &_op, const RuntimeObj *context) const + { + auto op = as(_op); + T *inptr = op->getInputs(0)->getRawDataPtr(); + T *outptr = op->getOutput()->getRawDataPtr(); + + auto outDim = op->getOutput()->getDims(); + auto n = op->getOutput()->size(); + + T (*_doCompute) + (T val); + switch (op->getOpType().underlying()) + { + case OpType::Relu: + _doCompute = reluCompute; + break; + default: + IT_TODO_HALT(); + } + + for (size_t offset = 0; offset < n; offset++) + { + outptr[offset] = _doCompute(inptr[offset]); + } + } + + void compute(const Operator &_op, + const RuntimeObj *context) const override + { +#define CASE(N) \ + case N: \ + doCompute::t>(_op, context) + + int dataTypeIdx = _op->getDType().getIndex(); + switch (dataTypeIdx) + { + CASE(1); // DataType::Float32 + break; + CASE(12); // DataType::UInt32 + break; + default: + IT_TODO_HALT(); + } + } + }; + + class Clip : public CpuKernelWithoutConfig + { + template + void doCompute(const Operator &_op, const RuntimeObj *context) const + { + auto op = as(_op); + T *inptr = op->getInputs(0)->getRawDataPtr(); + T *outptr = op->getOutput()->getRawDataPtr(); + auto minValue = op->getMin(); + auto maxValue = op->getMax(); + + auto n = op->getOutput()->size(); + for (size_t offset = 0; offset < n; offset++) + { + auto val = *inptr++; + *outptr++ = (minValue && val < *minValue) ? *minValue + : (maxValue && val > *maxValue) ? *maxValue + : val; + } + } + + void compute(const Operator &_op, + const RuntimeObj *context) const override + { +#define CASE(N) \ + case N: \ + doCompute::t>(_op, context) + + int dataTypeIdx = _op->getDType().getIndex(); + switch (dataTypeIdx) + { + CASE(1); // DataType::Float32 + break; + CASE(12); // DataType::UInt32 + break; + default: + IT_TODO_HALT(); + } + } + }; + + REGISTER_KERNEL(Device::CPU, OpType::Relu, NativeUnary, "reluNaive_CPU"); + REGISTER_KERNEL(Device::CPU, OpType::Clip, Clip, "Clip_CPU"); + +}; // namespace infini diff --git a/src/operators/concat.cc b/src/operators/concat.cc index d1963308..5c5712b0 100644 --- a/src/operators/concat.cc +++ b/src/operators/concat.cc @@ -1,38 +1,40 @@ -#include "operators/concat.h" -#include "utils/operator_utils.h" - -namespace infini { -ConcatObj::ConcatObj(GraphObj *graph, TensorVec inputs, Tensor output, int _dim) - : OperatorObj(OpType::Concat, inputs, {output}) { - int rank = inputs[0]->getRank(); - dim = get_real_axis(_dim, rank); - IT_ASSERT(checkValid(graph)); -} - -optional> ConcatObj::inferShape(const TensorVec &inputs) { - Shape dims = inputs[0]->getDims(); - auto rank = inputs[0]->getRank(); - - // =================================== 作业 =================================== - // TODO:修改 dims,返回正确的 concat 后的 shape - // REF: https://onnx.ai/onnx/operators/onnx__Concat.html#concat-13 - // =================================== 作业 =================================== - - return {{dims}}; -} - -std::string ConcatObj::toString() const { - std::ostringstream os; - os << "Concat[" << getGuid() << "]"; - os << "("; - for (auto input : inputs) - os << vecToString(input->getDims()) << ","; - os << "dim=" << dim << ","; - os << "input="; - for (auto input : inputs) - os << input->getGuid() << ","; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); -} - -} // namespace infini +#include "operators/concat.h" +#include "utils/operator_utils.h" + +namespace infini { +ConcatObj::ConcatObj(GraphObj *graph, TensorVec inputs, Tensor output, int _dim) + : OperatorObj(OpType::Concat, inputs, {output}) { + int rank = inputs[0]->getRank(); + dim = get_real_axis(_dim, rank); + IT_ASSERT(checkValid(graph)); +} + +optional> ConcatObj::inferShape(const TensorVec &inputs) { + Shape dims = inputs[0]->getDims(); + + // =================================== 作业 =================================== + // TODO:修改 dims,返回正确的 concat 后的 shape + // REF: https://onnx.ai/onnx/operators/onnx__Concat.html#concat-13 + // =================================== 作业 =================================== + for (size_t i = 1; i < inputs.size(); ++i) { + dims[dim] += inputs[i]->getDims()[dim]; + } + + return {{dims}}; +} + +std::string ConcatObj::toString() const { + std::ostringstream os; + os << "Concat[" << getGuid() << "]"; + os << "("; + for (auto input : inputs) + os << vecToString(input->getDims()) << ","; + os << "dim=" << dim << ","; + os << "input="; + for (auto input : inputs) + os << input->getGuid() << ","; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); +} + +} // namespace infini diff --git a/src/operators/element_wise.cc b/src/operators/element_wise.cc index c1b4ef1f..f9adb8f6 100644 --- a/src/operators/element_wise.cc +++ b/src/operators/element_wise.cc @@ -1,33 +1,33 @@ -#include "operators/element_wise.h" -#include "utils/operator_utils.h" - -namespace infini -{ - ElementWiseObj::ElementWiseObj(OpType type, GraphObj *graph, Tensor input0, - Tensor input1, Tensor output) - : OperatorObj(type, {input0, input1}, {output}) - { - IT_ASSERT(checkValid(graph)); - } - - optional> ElementWiseObj::inferShape(const TensorVec &inputs) - { - const auto A = inputs[0], B = inputs[1]; - auto res = infer_broadcast(A->getDims(), B->getDims()); - return {{res}}; - } - - std::string ElementWiseObj::toString() const - { - std::ostringstream os; - os << type.toString() << "[" << getGuid() << "]"; - os << "("; - os << vecToString(inputs[0]->getDims()) << ","; - os << vecToString(inputs[1]->getDims()) << ","; - os << "input0=" << inputs[0]->getGuid() << ","; - os << "input1=" << inputs[1]->getGuid() << ","; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); - } - -}; // namespace infini +#include "operators/element_wise.h" +#include "utils/operator_utils.h" + +namespace infini +{ + ElementWiseObj::ElementWiseObj(OpType type, GraphObj *graph, Tensor input0, + Tensor input1, Tensor output) + : OperatorObj(type, {input0, input1}, {output}) + { + IT_ASSERT(checkValid(graph)); + } + + optional> ElementWiseObj::inferShape(const TensorVec &inputs) + { + const auto A = inputs[0], B = inputs[1]; + auto res = infer_broadcast(A->getDims(), B->getDims()); + return {{res}}; + } + + std::string ElementWiseObj::toString() const + { + std::ostringstream os; + os << type.toString() << "[" << getGuid() << "]"; + os << "("; + os << vecToString(inputs[0]->getDims()) << ","; + os << vecToString(inputs[1]->getDims()) << ","; + os << "input0=" << inputs[0]->getGuid() << ","; + os << "input1=" << inputs[1]->getGuid() << ","; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); + } + +}; // namespace infini diff --git a/src/operators/matmul.cc b/src/operators/matmul.cc index 7a16ca27..b1cb9c33 100644 --- a/src/operators/matmul.cc +++ b/src/operators/matmul.cc @@ -1,33 +1,64 @@ -#include "operators/matmul.h" - -namespace infini -{ - - MatmulObj::MatmulObj(GraphObj *graph, Tensor A, Tensor B, Tensor C, bool transA, - bool transB) - : OperatorObj(OpType::MatMul, TensorVec{A, B}, {C}), - transA(transA), transB(transB) - { - IT_ASSERT(checkValid(graph)); - } - - string MatmulObj::toString() const - { - std::ostringstream os; - os << "Matmul([" << (transA ? "A^T" : "A") << "," << (transB ? "B^T" : "B]") - << ",A=" << inputs[0]->getGuid() - << ",B=" << inputs[1]->getGuid() << ",C=" << outputs[0]->getGuid() - << ",mnk=[" << m << "," << n << "," << k << "])"; - return os.str(); - } - - optional> MatmulObj::inferShape(const TensorVec &inputs) - { - // =================================== 作业 =================================== - // TODO:返回经过 matmul 操作后的 shape - // REF: https://github.com/onnx/onnx/blob/main/docs/Operators.md#gemm - // =================================== 作业 =================================== - return std::nullopt; - } - +#include "operators/matmul.h" + +namespace infini +{ + + MatmulObj::MatmulObj(GraphObj *graph, Tensor A, Tensor B, Tensor C, bool transA, + bool transB) + : OperatorObj(OpType::MatMul, TensorVec{A, B}, {C}), + transA(transA), transB(transB) + { + IT_ASSERT(checkValid(graph)); + } + + string MatmulObj::toString() const + { + std::ostringstream os; + os << "Matmul([" << (transA ? "A^T" : "A") << "," << (transB ? "B^T" : "B]") + << ",A=" << inputs[0]->getGuid() + << ",B=" << inputs[1]->getGuid() << ",C=" << outputs[0]->getGuid() + << ",mnk=[" << m << "," << n << "," << k << "])"; + return os.str(); + } + + optional> MatmulObj::inferShape(const TensorVec &inputs) + { + // =================================== 作业 =================================== + // TODO:返回经过 matmul 操作后的 shape + // REF: https://github.com/onnx/onnx/blob/main/docs/Operators.md#gemm + // =================================== 作业 =================================== + const auto A = inputs[0], B = inputs[1]; + auto shapeA = A->getDims(); + auto shapeB = B->getDims(); + int rankA = shapeA.size(); + int rankB = shapeB.size(); + + // Extract matrix dimensions + int aM = shapeA[rankA - 2], aK = shapeA[rankA - 1]; + int bK = shapeB[rankB - 2], bN = shapeB[rankB - 1]; + if (transA) + std::swap(aM, aK); + if (transB) + std::swap(bK, bN); + + m = aM; + n = bN; + k = aK; + + // Broadcast batch dimensions + int batchRank = std::max(rankA, rankB) - 2; + Shape outputShape(batchRank + 2); + for (int i = 0; i < batchRank; ++i) { + int dimA = (i < rankA - 2) ? shapeA[i] : 1; + int dimB = (i < rankB - 2) ? shapeB[i] : 1; + IT_ASSERT(dimA == dimB || dimA == 1 || dimB == 1, + "Incompatible batch dimensions for matmul"); + outputShape[i] = std::max(dimA, dimB); + } + outputShape[batchRank] = m; + outputShape[batchRank + 1] = n; + + return {{outputShape}}; + } + } // namespace infini \ No newline at end of file diff --git a/src/operators/transpose.cc b/src/operators/transpose.cc index faab2b69..d15d36af 100644 --- a/src/operators/transpose.cc +++ b/src/operators/transpose.cc @@ -1,50 +1,53 @@ -#include "operators/transpose.h" - -namespace infini -{ - TransposeObj::TransposeObj(GraphObj *graph, Tensor input, Tensor output, - vector permute) - : OperatorObj(OpType::Transpose, {input}, {output}) - { - auto rank = input->getRank(); - if (permute.empty()) - { - for (size_t i = 0; i < rank; ++i) - { - transposePermute[i] = i; - } - } - else - { - IT_ASSERT(rank == permute.size()); - transposePermute = std::move(permute); - } - IT_ASSERT(checkValid(graph)); - } - - optional> TransposeObj::inferShape(const TensorVec &inputs) - { - const auto A = inputs[0]; - auto input_dim = A->getDims(); - auto output_dim = input_dim; - int rank = A->getRank(); - - // =================================== 作业 =================================== - // TODO:修改 output_dim,返回正确的 transpose 后的 shape - // REF: https://onnx.ai/onnx/operators/onnx__Transpose.html#transpose-21 - // =================================== 作业 =================================== - - return std::nullopt; - } - - std::string TransposeObj::toString() const - { - std::ostringstream os; - os << type.toString() << "[" << getGuid() << "]"; - os << "("; - os << vecToString(inputs[0]->getDims()) << ","; - os << "input=" << inputs[0]->getGuid() << ","; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); - } -}; // namespace infini +#include "operators/transpose.h" + +namespace infini +{ + TransposeObj::TransposeObj(GraphObj *graph, Tensor input, Tensor output, + vector permute) + : OperatorObj(OpType::Transpose, {input}, {output}) + { + auto rank = input->getRank(); + if (permute.empty()) + { + for (size_t i = 0; i < rank; ++i) + { + transposePermute[i] = i; + } + } + else + { + IT_ASSERT(rank == permute.size()); + transposePermute = std::move(permute); + } + IT_ASSERT(checkValid(graph)); + } + + optional> TransposeObj::inferShape(const TensorVec &inputs) + { + const auto A = inputs[0]; + auto input_dim = A->getDims(); + auto output_dim = input_dim; + int rank = A->getRank(); + + // =================================== 作业 =================================== + // TODO:修改 output_dim,返回正确的 transpose 后的 shape + // REF: https://onnx.ai/onnx/operators/onnx__Transpose.html#transpose-21 + // =================================== 作业 =================================== + for (int i = 0; i < rank; ++i) { + output_dim[i] = input_dim[transposePermute[i]]; + } + + return {{output_dim}}; + } + + std::string TransposeObj::toString() const + { + std::ostringstream os; + os << type.toString() << "[" << getGuid() << "]"; + os << "("; + os << vecToString(inputs[0]->getDims()) << ","; + os << "input=" << inputs[0]->getGuid() << ","; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); + } +}; // namespace infini diff --git a/src/operators/unary.cc b/src/operators/unary.cc index 3daad361..b46db1a3 100644 --- a/src/operators/unary.cc +++ b/src/operators/unary.cc @@ -1,148 +1,148 @@ -#include "operators/unary.h" - -namespace infini -{ - UnaryObj::UnaryObj(OpType type, GraphObj *graph, Tensor input, Tensor output) - : OperatorObj(type, {input}, {output}) - { - IT_ASSERT(checkValid(graph)); - } - - optional> UnaryObj::inferShape(const TensorVec &inputs) - { - const auto A = inputs[0]; - return {{A->getDims()}}; - } - - std::string UnaryObj::toString() const - { - std::ostringstream os; - os << type.toString() << "[" << getGuid() << "]"; - os << "("; - os << vecToString(inputs[0]->getDims()) << ","; - os << "input=" << inputs[0]->getGuid() << ","; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); - } - - ClipObj::ClipObj(GraphObj *graph, Tensor input, Tensor output, - std::optional min, std::optional max) - : OperatorObj(OpType::Clip, {input}, {output}), minValue(min), - maxValue(max) - { - IT_ASSERT(checkValid(graph)); - } - - optional> ClipObj::inferShape(const TensorVec &inputs) - { - // =================================== 作业 =================================== - // TODO:返回经过 clip 操作后的 shape - // REF: https://onnx.ai/onnx/operators/onnx__Clip.html#clip-13 - // =================================== 作业 =================================== - return std::nullopt; - } - - std::string ClipObj::toString() const - { - std::ostringstream os; - os << type.toString() << "[" << getGuid() << "]"; - os << "("; - os << vecToString(inputs[0]->getDims()) << ","; - os << "input=" << inputs[0]->getGuid() << ","; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); - } - - CastObj::CastObj(GraphObj *graph, Tensor input, Tensor output, CastType type) - : OperatorObj(OpType::Cast, {input}, {output}), castType(type) - { - IT_ASSERT(checkValid(graph)); - } - - vector CastObj::inferDataType(const TensorVec &inputs) const - { - // =================================== 作业 =================================== - // TODO:返回经过 cast 操作后, 输出 tensor 的数目和数据类型 - // REF_FILE: src/core/operator.cc - // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 - // =================================== 作业 =================================== - return {}; - } - - optional> CastObj::inferShape(const TensorVec &inputs) - { - // =================================== 作业 =================================== - // TODO:返回经过 cast 操作后的 shape - // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 - // =================================== 作业 =================================== - return std::nullopt; - } - - std::string CastObj::toString() const - { - std::ostringstream os; - os << type.toString() << "[" << getGuid() << "]"; - os << "("; - os << "output=" << outputs[0]->getGuid() << ")"; - return os.str(); - } - - DataType CastObj::getOutputDataType() const - { - switch (castType) - { - case CastType::Float2Float16: - return DataType::Float16; - case CastType::Float2Int64: - return DataType::Int64; - case CastType::Float2Int32: - return DataType::Int32; - case CastType::Float2Int16: - return DataType::Int16; - case CastType::Float2Int8: - return DataType::Int8; - case CastType::Int322Float: - return DataType::Float32; - case CastType::Int322Int8: - return DataType::Int8; - case CastType::Int322Int16: - return DataType::Int16; - case CastType::Int162Float: - return DataType::Float32; - case CastType::Int162Int32: - return DataType::Int32; - case CastType::Int82Float: - return DataType::Float32; - case CastType::Int82Int16: - return DataType::Int16; - case CastType::Int82Int32: - return DataType::Int32; - case CastType::Uint82Float: - return DataType::Float32; - case CastType::Uint82Int32: - return DataType::Int32; - case CastType::Uint82Int64: - return DataType::Int64; - case CastType::Int322Int64: - return DataType::Int64; - case CastType::Int642Int32: - return DataType::Int32; - case CastType::Int642Uint32: - return DataType::UInt32; - case CastType::Int642Float: - return DataType::Float32; - case CastType::Uint322Int64: - return DataType::Int64; - case CastType::Float162Float: - return DataType::Float32; - case CastType::BFloat162Float: - return DataType::Float32; - case CastType::Float2BFloat16: - return DataType::BFloat16; - case CastType::Float2Float: - return DataType::Float32; - default: - IT_TODO_HALT(); - } - } -}; // namespace infini +#include "operators/unary.h" + +namespace infini +{ + UnaryObj::UnaryObj(OpType type, GraphObj *graph, Tensor input, Tensor output) + : OperatorObj(type, {input}, {output}) + { + IT_ASSERT(checkValid(graph)); + } + + optional> UnaryObj::inferShape(const TensorVec &inputs) + { + const auto A = inputs[0]; + return {{A->getDims()}}; + } + + std::string UnaryObj::toString() const + { + std::ostringstream os; + os << type.toString() << "[" << getGuid() << "]"; + os << "("; + os << vecToString(inputs[0]->getDims()) << ","; + os << "input=" << inputs[0]->getGuid() << ","; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); + } + + ClipObj::ClipObj(GraphObj *graph, Tensor input, Tensor output, + std::optional min, std::optional max) + : OperatorObj(OpType::Clip, {input}, {output}), minValue(min), + maxValue(max) + { + IT_ASSERT(checkValid(graph)); + } + + optional> ClipObj::inferShape(const TensorVec &inputs) + { + // =================================== 作业 =================================== + // TODO:返回经过 clip 操作后的 shape + // REF: https://onnx.ai/onnx/operators/onnx__Clip.html#clip-13 + // =================================== 作业 =================================== + return {{inputs[0]->getDims()}}; + } + + std::string ClipObj::toString() const + { + std::ostringstream os; + os << type.toString() << "[" << getGuid() << "]"; + os << "("; + os << vecToString(inputs[0]->getDims()) << ","; + os << "input=" << inputs[0]->getGuid() << ","; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); + } + + CastObj::CastObj(GraphObj *graph, Tensor input, Tensor output, CastType type) + : OperatorObj(OpType::Cast, {input}, {output}), castType(type) + { + IT_ASSERT(checkValid(graph)); + } + + vector CastObj::inferDataType(const TensorVec &inputs) const + { + // =================================== 作业 =================================== + // TODO:返回经过 cast 操作后, 输出 tensor 的数目和数据类型 + // REF_FILE: src/core/operator.cc + // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 + // =================================== 作业 =================================== + return {getOutputDataType()}; + } + + optional> CastObj::inferShape(const TensorVec &inputs) + { + // =================================== 作业 =================================== + // TODO:返回经过 cast 操作后的 shape + // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 + // =================================== 作业 =================================== + return {{inputs[0]->getDims()}}; + } + + std::string CastObj::toString() const + { + std::ostringstream os; + os << type.toString() << "[" << getGuid() << "]"; + os << "("; + os << "output=" << outputs[0]->getGuid() << ")"; + return os.str(); + } + + DataType CastObj::getOutputDataType() const + { + switch (castType) + { + case CastType::Float2Float16: + return DataType::Float16; + case CastType::Float2Int64: + return DataType::Int64; + case CastType::Float2Int32: + return DataType::Int32; + case CastType::Float2Int16: + return DataType::Int16; + case CastType::Float2Int8: + return DataType::Int8; + case CastType::Int322Float: + return DataType::Float32; + case CastType::Int322Int8: + return DataType::Int8; + case CastType::Int322Int16: + return DataType::Int16; + case CastType::Int162Float: + return DataType::Float32; + case CastType::Int162Int32: + return DataType::Int32; + case CastType::Int82Float: + return DataType::Float32; + case CastType::Int82Int16: + return DataType::Int16; + case CastType::Int82Int32: + return DataType::Int32; + case CastType::Uint82Float: + return DataType::Float32; + case CastType::Uint82Int32: + return DataType::Int32; + case CastType::Uint82Int64: + return DataType::Int64; + case CastType::Int322Int64: + return DataType::Int64; + case CastType::Int642Int32: + return DataType::Int32; + case CastType::Int642Uint32: + return DataType::UInt32; + case CastType::Int642Float: + return DataType::Float32; + case CastType::Uint322Int64: + return DataType::Int64; + case CastType::Float162Float: + return DataType::Float32; + case CastType::BFloat162Float: + return DataType::Float32; + case CastType::Float2BFloat16: + return DataType::BFloat16; + case CastType::Float2Float: + return DataType::Float32; + default: + IT_TODO_HALT(); + } + } +}; // namespace infini diff --git a/src/utils/exception.cc b/src/utils/exception.cc index 228a39a8..c1d3814d 100644 --- a/src/utils/exception.cc +++ b/src/utils/exception.cc @@ -1,5 +1,5 @@ -#include "utils/exception.h" - -namespace infini { -Exception::Exception(const std::string &msg) : std::runtime_error(msg) {} -} // namespace infini +#include "utils/exception.h" + +namespace infini { +Exception::Exception(const std::string &msg) : std::runtime_error(msg) {} +} // namespace infini diff --git a/src/utils/operator_utils.cc b/src/utils/operator_utils.cc index edbd2c82..e68ee4ea 100644 --- a/src/utils/operator_utils.cc +++ b/src/utils/operator_utils.cc @@ -1,69 +1,80 @@ -#include "utils/operator_utils.h" -#include "core/runtime.h" - -namespace infini { - -Shape infer_broadcast(const Shape &A, const Shape &B) { - - // =================================== 作业 =================================== - // TODO:对 A 和 B 进行双向广播,返回广播后的形状。 - // REF: https://github.com/onnx/onnx/blob/main/docs/Broadcasting.md - // =================================== 作业 =================================== - - return {}; -} - -int get_real_axis(const int &axis, const int &rank) { - IT_ASSERT(rank >= 1); - IT_ASSERT(axis >= -rank && axis <= (rank - 1)); - int newAxis; - if (axis < 0) { - newAxis = rank + axis; - } else { - newAxis = axis; - } - return newAxis; -} - -Shape locate_index(size_t inputN, const Shape &shape) { - Shape ans(shape.size()); - auto i = ans.rbegin(); - auto j = shape.rbegin(), ej = shape.rend(); - while (j != ej) { - auto div = std::div(inputN, *j++); - *i++ = div.rem; - inputN = div.quot; - } - return ans; -} - -size_t delocate_index(const Shape &shapeIndex, const Shape &shape, - const Shape &stride) { - size_t ans = 0; - Shape index(shapeIndex.size()); - IT_ASSERT(shapeIndex.size() == shape.size()); - IT_ASSERT(shape.size() == stride.size()); - for (size_t i = 0; i < shape.size(); ++i) { - index[i] = shapeIndex[i] % shape[i]; - ans += index[i] * stride[i]; - } - return ans; -} - -std::string device_to_str(Device device) { - std::string deviceStr; - switch (device) { - case Device::CPU: - return "CPU"; - default: - IT_TODO_HALT(); - } -} - -std::string get_kernel_attrs_str(const KernelAttrs &kernelAttrs) { - std::string deviceStr = device_to_str(std::get<0>(kernelAttrs)); - std::string opStr = OpType(std::get<1>(kernelAttrs)).toString(); - return deviceStr + ", " + opStr; -} - -} // namespace infini +#include "utils/operator_utils.h" +#include "core/runtime.h" + +namespace infini { + +Shape infer_broadcast(const Shape &A, const Shape &B) { + + // =================================== 作业 =================================== + // TODO:对 A 和 B 进行双向广播,返回广播后的形状。 + // REF: https://github.com/onnx/onnx/blob/main/docs/Broadcasting.md + // =================================== 作业 =================================== + + auto rankA = A.size(); + auto rankB = B.size(); + auto rank = std::max(rankA, rankB); + Shape result(rank); + for (size_t i = 0; i < rank; ++i) { + auto dimA = (i < rank - rankA) ? 1 : A[i - (rank - rankA)]; + auto dimB = (i < rank - rankB) ? 1 : B[i - (rank - rankB)]; + IT_ASSERT(dimA == dimB || dimA == 1 || dimB == 1, + "Incompatible broadcast shapes"); + result[i] = std::max(dimA, dimB); + } + return result; +} + +int get_real_axis(const int &axis, const int &rank) { + IT_ASSERT(rank >= 1); + IT_ASSERT(axis >= -rank && axis <= (rank - 1)); + int newAxis; + if (axis < 0) { + newAxis = rank + axis; + } else { + newAxis = axis; + } + return newAxis; +} + +Shape locate_index(size_t inputN, const Shape &shape) { + Shape ans(shape.size()); + auto i = ans.rbegin(); + auto j = shape.rbegin(), ej = shape.rend(); + while (j != ej) { + auto div = std::div(inputN, *j++); + *i++ = div.rem; + inputN = div.quot; + } + return ans; +} + +size_t delocate_index(const Shape &shapeIndex, const Shape &shape, + const Shape &stride) { + size_t ans = 0; + Shape index(shapeIndex.size()); + IT_ASSERT(shapeIndex.size() == shape.size()); + IT_ASSERT(shape.size() == stride.size()); + for (size_t i = 0; i < shape.size(); ++i) { + index[i] = shapeIndex[i] % shape[i]; + ans += index[i] * stride[i]; + } + return ans; +} + +std::string device_to_str(Device device) { + std::string deviceStr; + switch (device) { + case Device::CPU: + return "CPU"; + default: + IT_TODO_HALT(); + } +} + +std::string get_kernel_attrs_str(const KernelAttrs &kernelAttrs) { + std::string deviceStr = device_to_str(std::get<0>(kernelAttrs)); + std::string opStr = OpType(std::get<1>(kernelAttrs)).toString(); + return deviceStr + ", " + opStr; +} + +} // namespace infini From 4b6395bcfc2deb1dc034c6349c88c961ff28a88e Mon Sep 17 00:00:00 2001 From: ljc66d <1814007452@qq.com> Date: Thu, 13 Aug 2026 00:03:30 +0800 Subject: [PATCH 2/2] Add files via upload --- "\346\210\252\345\233\276.png" | Bin 0 -> 100943 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "\346\210\252\345\233\276.png" diff --git "a/\346\210\252\345\233\276.png" "b/\346\210\252\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..41e815be563b1e7380f1639ab21e22f6b7f07edd GIT binary patch literal 100943 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

oZ9#NUnW3S)ImC$J8vqqe+ZfXNSvMjtE-zk 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 literal 0 HcmV?d00001