diff --git a/helper/README.md b/helper/README.md new file mode 100644 index 0000000..7c9ebd7 --- /dev/null +++ b/helper/README.md @@ -0,0 +1 @@ +This dir is for understanding the source files by greating some test or helper scripts. \ No newline at end of file diff --git a/helper/TensorObj.cpp b/helper/TensorObj.cpp new file mode 100644 index 0000000..bc70a15 --- /dev/null +++ b/helper/TensorObj.cpp @@ -0,0 +1,12 @@ +#include "core/tensor.h" +#include + +using namespace infini; + +TensorObj t; + +int main() { + std::cout << t << std::endl; + return 0; +} + diff --git a/include/core/allocator.h b/include/core/allocator.h index 002601d..0901311 100644 --- a/include/core/allocator.h +++ b/include/core/allocator.h @@ -28,6 +28,77 @@ namespace infini { // HINT: 可以使用一个 map 来存储 free block,key 为 block 的起始/结尾地址,value 为 block 的大小 // =================================== 作业 =================================== + // 尝试了 first-fit,一个 map 的 best-fit + // 最后还是参考了 InfiniTensor 的实现。 + // 笔记见 https://scbz4learning.github.io/Infinitensor/Stage_1/5_TinyInfiniTensor.md + + // Finding a suitable block need O(n), as only linear search is available. + // std::map free_blocks; + + // However, referring to the original project + // Using an ordered set to sort the blocksize from smallest to largest + // can reduce the time in O(log n) + // And the blockAddr can use the hash table with doubly linked list + // to avoid maintain multiple datastructures + + // 这样也不行 + // 一个是必须要维护尾指针块信息,不然就没法知道扩容要不要merge + // 一个是unorder_map 存储addrInfo是不能找到临近块的 + // map能,但是O(n)了 + // struct BlockInfo { + // size_t addr; // 当前块的起始地址 + // size_t size; // 当前块的大小 + // size_t prevAddr; // 前一个块的起始地址,如果没有则设为 SIZE_MAX + // size_t nextAddr; // 后一个块的起始地址,如果没有则设为 SIZE_MAX + + // // unordered_map needs default constructer + // BlockInfo() + // : addr(0), size(0), prevAddr(SIZE_MAX), nextAddr(SIZE_MAX) {} + + // BlockInfo(size_t a, size_t s, size_t prev = SIZE_MAX, size_t next = SIZE_MAX) + // : addr(a), size(s), prevAddr(prev), nextAddr(next) {} + + // // overload < + // bool operator<(const BlockInfo &other) const { + // if (size != other.size) return size < other.size; + // return addr < other.addr; + // } + // }; + + // // balanced tree ordered by size in descending order + // std::set freeBlocks; + // // hash map to find block info by addr + // // key addr + // // value blockInfo + // std::unordered_map addrInfo; + + // // add a tailblock pointer for merging when increasing peak + // BlockInfo* tailBlock = nullptr; + + struct BlockInfo { + size_t addr_; + size_t size_; + + BlockInfo() : addr_(0), size_(0){} + BlockInfo(size_t s) : addr_(0), size_(s){} + BlockInfo(size_t a, size_t s): addr_(a), size_(s){} + + bool operator<(const BlockInfo &other) const { + return (size_ != other.size_) ? (size_ < other.size_) : (addr_ < other.addr_); + } + }; + + // Balanced tree for free memory blocks, sorted by size and address + std::set freeBlocks; + + // Key: Starting address of the free memory block + // Value: Size of the block + std::unordered_map blockStartToSize; + + // Key: Ending address of the free memory block + // Value: Size of the block + std::unordered_map blockEndToSize; + public: Allocator(Runtime runtime); diff --git a/src/core/allocator.cc b/src/core/allocator.cc index ff593ae..0818eea 100644 --- a/src/core/allocator.cc +++ b/src/core/allocator.cc @@ -23,6 +23,88 @@ namespace infini } } + // 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: 设计一个算法来分配内存,返回起始地址偏移量 + // // =================================== 作业 =================================== + // size_t offset = 0; + + // // iterate through free blocks (first-fit / best-fit) + // auto it = freeBlocks.lower_bound(BlockInfo(0, size)); // find first block >= size + // if (it != freeBlocks.end()) { + // BlockInfo block = *it; + // offset = block.addr; + + // // remove old block + // freeBlocks.erase(it); + // addrInfo.erase(block.addr); + + // // split if block is larger than needed + // if (block.size > size) { + // BlockInfo newBlock(block.addr + size, block.size - size, offset, block.nextAddr); + // if (block.nextAddr != SIZE_MAX) { + // addrInfo[block.nextAddr].prevAddr = newBlock.addr; + // } else { + // tailBlock = &newBlock; + // } + // freeBlocks.insert(newBlock); + // addrInfo[newBlock.addr] = newBlock; + // } else { + // // update prev/next of neighbors if the block is removed + // if (block.prevAddr != SIZE_MAX) { + // addrInfo[block.prevAddr].nextAddr = block.nextAddr; + // } + // if (block.nextAddr != SIZE_MAX) { + // addrInfo[block.nextAddr].prevAddr = block.prevAddr; + // } else { + // tailBlock = &addrInfo[block.prevAddr]; + // } + // } + // } else { + // // no suitable free block found in freeBlocks + // if (tailBlock && tailBlock->addr + tailBlock->size == peak) { + // // Merge new allocation with the current tail block + // freeBlocks.erase(*tailBlock); + // tailBlock->size += size; + // freeBlocks.insert(*tailBlock); + // offset = tailBlock->addr; + // // peak doesn't need to be increased since it's already included + // } else { + // // Extend peak: create a new block at the end + // offset = peak; + // BlockInfo newBlock(peak, size, tailBlock ? tailBlock->addr : SIZE_MAX, SIZE_MAX); + // if (tailBlock) { + // tailBlock->nextAddr = newBlock.addr; // link previous tail + // } + // addrInfo[newBlock.addr] = newBlock; + // tailBlock = &addrInfo[newBlock.addr]; // update tailBlock pointer + // peak += size; // move peak to include new allocation + // } + // } + + // used += size; + // return offset; + // } + + // void Allocator::free(size_t addr, size_t size) + // { + // IT_ASSERT(this->ptr == nullptr); + // size = getAlignedSize(size); + + // // =================================== 作业 =================================== + // // TODO: 设计一个算法来回收内存 + // // =================================== 作业 =================================== + // BlockInfo newBlock(addr, size, SIZE_MAX, SIZE_MAX); + + // auto it = freeBlocks.lower_bound(addr); + + // } + size_t Allocator::alloc(size_t size) { IT_ASSERT(this->ptr == nullptr); @@ -33,7 +115,65 @@ namespace infini // TODO: 设计一个算法来分配内存,返回起始地址偏移量 // =================================== 作业 =================================== - return 0; + size_t retAddr; + auto it = freeBlocks.lower_bound(BlockInfo(size)); + + // 如果找到符合条件的空闲块 + if (it != freeBlocks.end()) { + BlockInfo bestFitBlock = *it; + retAddr = bestFitBlock.addr_; + + // 删除旧块 + freeBlocks.erase(it); + blockStartToSize.erase(bestFitBlock.addr_); + blockEndToSize.erase(bestFitBlock.addr_ + bestFitBlock.size_); + + // 如果剩下碎片 + if (bestFitBlock.size_ > size){ + // 更新空闲块列表 + BlockInfo remainBlock = {bestFitBlock.addr_ + size, bestFitBlock.size_ - size}; + freeBlocks.insert(remainBlock); + blockStartToSize[remainBlock.addr_] = remainBlock.size_; + blockEndToSize[remainBlock.addr_ + remainBlock.size_] = remainBlock.size_; + } + + // 更新已使用内存 + used += size; + + // 返回分配的起始地址 + return retAddr; + } + else { + // 没有找到大小够的空闲块,尝试扩展内存池 + // 如果尾块存在,要合并 + auto itEnd = blockEndToSize.find(peak); + if (itEnd != blockEndToSize.end()) { + size_t endBlockSize = itEnd->second; + + // 更新freeBlocks + BlockInfo endBlock(peak - endBlockSize, endBlockSize); + freeBlocks.erase(endBlock); + + // 更新 hash map + blockStartToSize.erase(endBlock.addr_); + blockEndToSize.erase(peak); + + // Update used & peak, just margin + used += size - endBlockSize; + peak += size - endBlockSize; + + // retAddr + retAddr = endBlock.addr_; + } else { + // retAddr + retAddr = peak; + + // Update used & peak + used += size; + peak += size; + } + } + return retAddr; } void Allocator::free(size_t addr, size_t size) @@ -44,8 +184,39 @@ namespace infini // =================================== 作业 =================================== // TODO: 设计一个算法来回收内存 // =================================== 作业 =================================== + + BlockInfo newBlock(addr, size); + + // 如果有邻接前块,合并并删除前块 + auto itAdjBlock = blockEndToSize.find(addr); + if (itAdjBlock != blockEndToSize.end()) { + // 拓展 newBlock 空间 + newBlock.addr_ -= itAdjBlock->second; + newBlock.size_ += itAdjBlock->second; + + // 删除 prev block + freeBlocks.erase(BlockInfo(itAdjBlock->first - itAdjBlock->second, + itAdjBlock->second)); + blockStartToSize.erase(itAdjBlock->first - itAdjBlock->second); + blockEndToSize.erase(itAdjBlock->first); + } + + // 如果有邻接后块,合并并删除后块 + itAdjBlock = blockStartToSize.find(newBlock.addr_ + newBlock.size_); + if (itAdjBlock != blockStartToSize.end()) { + newBlock.size_ += itAdjBlock->second; + + freeBlocks.erase(BlockInfo(itAdjBlock->first, itAdjBlock->second)); + blockStartToSize.erase(itAdjBlock->first); + blockEndToSize.erase(itAdjBlock->first + itAdjBlock->second); + } + + freeBlocks.emplace(newBlock); + blockStartToSize.emplace(newBlock.addr_, newBlock.size_); + blockEndToSize.emplace(newBlock.addr_ + newBlock.size_, newBlock.size_); } + void *Allocator::getPtr() { if (this->ptr == nullptr) diff --git a/src/core/graph.cc b/src/core/graph.cc index 3a90637..118dea5 100644 --- a/src/core/graph.cc +++ b/src/core/graph.cc @@ -1,4 +1,6 @@ #include "core/graph.h" +#include "operators/transpose.h" +#include "operators/matmul.h" #include #include #include @@ -98,6 +100,83 @@ namespace infini return this->sorted = true; } + // 没 lock + // void GraphObj::optimize() + // { + // // =================================== 作业 =================================== + // // TODO: 设计一个算法来实现指定的图优化规则 + // // 图优化规则如下: + // // 1. 去除冗余的算子(例如,两个相邻的算子都是 transpose 算子,且做的是相反的操作,可以将其全部删除) + // // 2. 合并算子(例如,矩阵乘算子中含有属性transA、transB,如果其输入存在transpose,且对最后两个维度做交换,就可以将transpose融入到矩阵乘算子的属性中去) + // // =================================== 作业 =================================== + + // // 看 test 好像 只做 transpose 优化 + // IT_ASSERT(topo_sort() == true); + + // // 如果第一个是T,最后一个op肯定不需要优化 + // for (auto itOp = ops.begin(); itOp < ops.end()-1;) { + // if ((*itOp)->getOpType() == OpType::Transpose) { + // // T -> T + // // T has 1 output and input only + // if ((*itOp)->getSuccessors()[0]->getOpType() == OpType::Transpose) { + // // 只有 replaceInput 接口 + // // 说明改后不改前 + // // // 前算子改 outputs + // // (*itOp)->getPredecessors()[0]->; + + // // 改前张量targets + // (*itOp)->getInputs()[0]->removeTarget((*itOp)); + // (*itOp)->getInputs()[0]->addTarget( + // (*itOp)->getSuccessors()[0]->getSuccessors()[0] + // ); + + // // 改后后算子input + // (*itOp)->getSuccessors()[0]->getSuccessors()[0]->replaceInput( + // (*itOp)->getSuccessors()[0]->getOutput(0), + // (*itOp)->getInputs()[0] + // ); + + // // remove 本算子output 张量 和 后算子output张量 + // removeTensor((*itOp)->getOutput(0)); + // removeTensor((*itOp)->getSuccessors()[0]->getOutput(0)); + + // // remove 本算子 和 后算子 + // itOp = ops.erase(itOp); + // itOp = ops.erase(itOp); + // } + // else if ((*itOp)->getSuccessors()[0]->getOpType() == OpType::MatMul) { + // // 改前张量targets + // (*itOp)->getInputs()[0]->removeTarget((*itOp)); + // (*itOp)->getInputs()[0]->addTarget( + // (*itOp)->getSuccessors()[0] + // ); + + // // 改后算子input + // auto succ = (*itOp)->getSuccessors()[0]; + // auto matmul = std::dynamic_pointer_cast(succ); + // auto transposeOutputTensor = matmul->getInputs(); + // if (transposeOutputTensor[0] == (*itOp)->getOutput(0)) { + // matmul->setTransA(!matmul->getTransA()); + // } else if (transposeOutputTensor[1] == (*itOp)->getOutput(0)) { + // matmul->setTransB(!matmul->getTransB()); + // } + + // // remove 本算子output 张量 + // removeTensor((*itOp)->getOutput(0)); + + // // remove 本算子 + // itOp = ops.erase(itOp); + // } + // else { + // itOp++; + // } + // } else { + // itOp++; + // } + // } + // } + + // Tensor 和 Op 是不一样的,tensor中 source 和 targets 是 WRef,要lock void GraphObj::optimize() { // =================================== 作业 =================================== @@ -106,6 +185,95 @@ namespace infini // 1. 去除冗余的算子(例如,两个相邻的算子都是 transpose 算子,且做的是相反的操作,可以将其全部删除) // 2. 合并算子(例如,矩阵乘算子中含有属性transA、transB,如果其输入存在transpose,且对最后两个维度做交换,就可以将transpose融入到矩阵乘算子的属性中去) // =================================== 作业 =================================== + + // 看 test 好像 只做 transpose 优化 + IT_ASSERT(topo_sort() == true); + + // 如果第一个是T,最后一个op肯定不需要优化 + for (auto itOp = ops.begin(); itOp < ops.end()-1;) { + if ((*itOp)->getOpType() == OpType::Transpose) { + auto op_output = (*itOp)->getOutput(); + auto op_input = (*itOp)->getInputs()[0]; + + // T -> T + // T has 1 output and input only + if ((*itOp)->getSuccessors()[0]->getOpType() == OpType::Transpose) { + auto succ = (*itOp)->getSuccessors()[0]; + auto succ_succ = succ->getSuccessors()[0]; + + // 只有 replaceInput 接口 + // 说明改后不改前 + // // 前算子改 outputs + // (*itOp)->getPredecessors()[0]->; + + // 改前张量targets + (*itOp)->getInputs()[0]->removeTarget((*itOp)); + (*itOp)->getInputs()[0]->addTarget( + (*itOp)->getSuccessors()[0]->getSuccessors()[0] + ); + + // 改后后算子input + (*itOp)->getSuccessors()[0]->getSuccessors()[0]->replaceInput( + (*itOp)->getSuccessors()[0]->getOutput(0), + (*itOp)->getInputs()[0] + ); + + // remove 本算子output 张量 和 后算子output张量 + removeTensor((*itOp)->getOutput(0)); + removeTensor((*itOp)->getSuccessors()[0]->getOutput(0)); + + // remove 本算子 和 后算子 + // 先断开关系再删除算子 + for (auto pred : (*itOp)->getPredecessors()) { + pred->removeSuccessors(*itOp); + } + for (auto succ_pred : succ->getSuccessors()) { + succ_pred->removePredecessors(succ); + } + + itOp = ops.erase(itOp); + itOp = ops.erase(itOp); + } + else if ((*itOp)->getSuccessors()[0]->getOpType() == OpType::MatMul) { + auto succ = (*itOp)->getSuccessors()[0]; // MatMul + auto matmul = std::dynamic_pointer_cast(succ); + + auto trans_input = (*itOp)->getInputs()[0]; + auto trans_output = (*itOp)->getOutput(); + + // 改后算子input + if (matmul->getInputs()[0] == trans_output) { + matmul->setTransA(!matmul->getTransA()); + matmul->replaceInput(trans_output, trans_input); + } else if (matmul->getInputs()[1] == trans_output) { + matmul->setTransB(!matmul->getTransB()); + matmul->replaceInput(trans_output, trans_input); + } + + // 改后算子input + trans_input->removeTarget(*itOp); + trans_input->addTarget(matmul); + + // remove 本算子output 张量 + removeTensor((*itOp)->getOutput(0)); + + // remove 本算子 + // 断开算子关系再删除 + for (auto pred : (*itOp)->getPredecessors()) { + pred->removeSuccessors(*itOp); + } + for (auto succ_succ : (*itOp)->getSuccessors()) { + succ_succ->removePredecessors(*itOp); + } + itOp = ops.erase(itOp); + } + else { + itOp++; + } + } else { + itOp++; + } + } } Tensor GraphObj::getTensor(int fuid) const @@ -152,6 +320,69 @@ namespace infini // TODO:利用 allocator 给计算图分配内存 // HINT: 获取分配好的内存指针后,可以调用 tensor 的 setDataBlob 函数给 tensor 绑定内存 // =================================== 作业 =================================== + // 这里逻辑还是没顺清楚 + // 静态分配之后,哪里free了? + // - 析构的时候!Ref为0,自动析构,析构销毁 X 析构函数中没有 + // 为什么先后绑定带来差异? + + // 这里有点像制作纸带: + // 应该按照这个逻辑来分配内存哦,后面计算的时候就用我给的offset去申请内存 + // 所以理论上是可以动态分配的。 + // 因为中间变量一定再输出的时候产生,最后一次输入的时候销毁 + // 用hash map计数,遍历op,每个op输出的tensor在hash map 不存在就alloc + // 存在就++。输入的tensor -- ,如果归零就free + // 原版的Infinitensor也是这样做的 + // 然而这里有个问题,最初的输入没法alloc + // 因为原版有isWeight(),这里没有。所以只能静态分配不复用 + // 不过allocator也不是没用,因为一个allocator可以管多个图,多个图之间还是可以复用的 + + // // 用来记录每个 tensor 的起始偏移 + // std::unordered_map tensorToOffset; + + // // 遍历所有 tensor,静态分配内存 + // for (auto &tensor : tensors) { + // // 分配内存 + // size_t offset = allocator.alloc(tensor->getBytes()); + // tensorToOffset[tensor] = offset; + + // // 立即绑定 Blob + // auto blob = make_ref(runtime, allocator.getPtr() + offset); + // tensor->setDataBlob(blob); + // } + + + // 立即绑定 Blob 不能通过test,因为同时分配了同一段内存吗?但是之后再申请也会这样不是吗? + // 有那个函数调用free了吗? + // std::unordered_map tensorToOffset; + // + // // 遍历所有 tensor,静态分配内存 + // for (auto &tensor : tensors) { + // // 分配内存 + // size_t offset = allocator.alloc(tensor->getBytes()); + // tensorToOffset[tensor.get()] = offset; // 注意这里用裸指针作为 key + // + // // 立即绑定 Blob + // auto blob = make_ref(runtime, allocator.getPtr() + offset); + // tensor->setDataBlob(blob); + // } + + + // 如果不能遍历的时候立即绑定,这里好像用 vector 更好 + std::unordered_map tensorToOffset; + + for (auto &tensor : tensors) { + size_t offset = allocator.alloc(tensor->getBytes()); + tensorToOffset[tensor] = offset; + } + + auto ptr = allocator.getPtr(); + + for (auto &kv : tensorToOffset) { + auto &tensor = kv.first; + size_t offset = kv.second; + auto blob = make_ref(runtime, ptr + offset); + tensor->setDataBlob(blob); + } allocator.info(); } diff --git a/src/operators/concat.cc b/src/operators/concat.cc index d196330..20e5142 100644 --- a/src/operators/concat.cc +++ b/src/operators/concat.cc @@ -18,6 +18,38 @@ optional> ConcatObj::inferShape(const TensorVec &inputs) { // REF: https://onnx.ai/onnx/operators/onnx__Concat.html#concat-13 // =================================== 作业 =================================== + // ~~没必要 check 了~~ + // 还是要check, checkValid 不检查内部,只看最后的合不合理 + // for (size_t i = 1; i < inputs.size(); i++) { + // IT_ASSERT(rank == inputs[i]->getRank()); + // for (size_t j = 0; j < rank; j++) { + // // 这里报错了,j 是 size_t 但是 dim 是 int + // // 为啥呢?负索引吧!加 rank 然后 assert 下 + // if (j == dim) + // dims[j] += inputs[i]->getDims()[j]; + // else + // IT_ASSERT(dims[j] == inputs[i]->getDims()[j]); + // } + // } + + if (rank + dim < 0) { + IT_ASSERT(1) << "dim is negative and out of bound"; + } else if (rank - dim < 0) { + IT_ASSERT(1) << "dim is too large"; + } + + size_t realDim = dim < 0 ? rank + dim : dim; + + for (size_t i = 1; i < inputs.size(); i++) { + IT_ASSERT(rank == inputs[i]->getRank()); + for (size_t j = 0; j < rank; j++) { + if (j == realDim) + dims[j] += inputs[i]->getDims()[j]; + else + IT_ASSERT(dims[j] == inputs[i]->getDims()[j]); + } + } + return {{dims}}; } diff --git a/src/operators/matmul.cc b/src/operators/matmul.cc index 7a16ca2..c643df9 100644 --- a/src/operators/matmul.cc +++ b/src/operators/matmul.cc @@ -27,7 +27,38 @@ namespace infini // TODO:返回经过 matmul 操作后的 shape // REF: https://github.com/onnx/onnx/blob/main/docs/Operators.md#gemm // =================================== 作业 =================================== - return std::nullopt; + // 看 test,input 会传 A, B, C (output, 可以nullptr), transA (bool = false), transB (bool = false) + // IT_ASSERT(inputs.size() != 2) << "There should be 2 matrices"; + + Shape A = inputs[0]->getDims(); + Shape B = inputs[1]->getDims(); + + IT_ASSERT(A.size() >=2 && B.size() >= 2); + + if (getTransA()) { + std::swap(A[A.size() - 1], A[A.size() - 2]); + } + if (getTransB()) { + std::swap(B[B.size() - 1], B[B.size() - 2]); + } + + Shape retShape(std::max(A.size(), B.size())); + + IT_ASSERT(A[A.size()-1] == B[B.size()-2]) \ + << "The mismatched n in (.., m n) (.., n,k)"; + retShape[retShape.size() - 1] = B[B.size() - 1]; + retShape[retShape.size() - 2] = A[A.size() - 2]; + + // broadcast + for (size_t i = 2; i < retShape.size(); i++) { + size_t dimA = i < A.size() ? A[A.size() - 1 - i] : 1; + size_t dimB = i < B.size() ? B[B.size() - 1 - i] : 1; + IT_ASSERT(dimA == dimB || dimA == 1 || dimB == 1) + << "mismatch dim in broadcasting"; + retShape[retShape.size() - 1 - i] = dimA != 1 ? dimA : dimB; + } + + return vector{ retShape }; } } // namespace infini \ No newline at end of file diff --git a/src/operators/transpose.cc b/src/operators/transpose.cc index faab2b6..28c2ae8 100644 --- a/src/operators/transpose.cc +++ b/src/operators/transpose.cc @@ -34,7 +34,20 @@ namespace infini // REF: https://onnx.ai/onnx/operators/onnx__Transpose.html#transpose-21 // =================================== 作业 =================================== - return std::nullopt; + // 来不及看 onnx 了 + // TransposeObj 有一个 std::vector getPermute() const { return transposePermute; } + // 所以直接修改这个就行。 + // 课上有说过,可以先存列有限矩阵,等到实际需要或者图优化的时候,在根据需要决定是否反转 + + + // perm_ 实际上就是维度标识 + // 比如shape 是 【2,3,4】 + // perm 是 【1,0,2】 + // 那就表示 第0维实际是第1维,以此类推 + for (auto i = 0; i < rank; ++i) + output_dim[i] = input_dim[transposePermute[i]]; + + return vector{output_dim}; } std::string TransposeObj::toString() const diff --git a/src/operators/unary.cc b/src/operators/unary.cc index 3daad36..0710562 100644 --- a/src/operators/unary.cc +++ b/src/operators/unary.cc @@ -39,7 +39,23 @@ namespace infini // TODO:返回经过 clip 操作后的 shape // REF: https://onnx.ai/onnx/operators/onnx__Clip.html#clip-13 // =================================== 作业 =================================== - return std::nullopt; + + // `clip` is used for bond the element within max and min + // so the shape is not augmented + + // 这个写法能通过test,但是做后面test的时候发现不太对 + // 因为这里语法是和onnx对齐的,clip接受的是 + // [实际tensor, min tensor, max tensor] + // 所以这样返回的是 [tensor dim, argument_min dim, argument_max dim] + // 后两个肯定没用 + // 这里名字叫unary,所以显然是接受一个tensor的 + // vector retDims; + // for (auto tensor : inputs){ + // retDims.push_back(tensor->getDims()); + // } + // return retDims; + + return vector{inputs[0]->getDims()}; } std::string ClipObj::toString() const @@ -66,7 +82,33 @@ namespace infini // REF_FILE: src/core/operator.cc // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 // =================================== 作业 =================================== - return {}; + + // 找不到ONNX的实现 + // 文档上说input 只有一个tensor + // 另外有两个 attributes + // 但是C++没有attributes + // 只能通过test 来猜测inputs + // auto op = g->addOp(i0, nullptr, CastType::Float2Float16); + // CastObj(GraphObj *graph, Tensor input, Tensor output, CastType type); + // CastObj::CastObj(GraphObj *graph, Tensor input, Tensor output, CastType type) + // CastObj::CastObj(GraphObj *graph, Tensor input, Tensor output, CastType type) + // : OperatorObj(OpType::Cast, {input}, {output}), castType(type) + // { + // IT_ASSERT(checkValid(graph)); + // } + // 所以返回 private 成员 castType 就行 + + // 应该是一堆tensor,type一样,所以用第一个代替? + // 不是,CastObj 定义就一个 + // int numInputs() const override { return 1; } + // int numOutputs() const override { return 1; } + + // castType 和 DataType 枚举类型不同!!! + // return { DataType(static_cast(castType)) }; + + // castType 是 x to y 的形式,所以有专用函数吗 + // 有,下面的DataType CastObj::getOutputDataType() const + return { getOutputDataType() }; } optional> CastObj::inferShape(const TensorVec &inputs) @@ -75,7 +117,9 @@ namespace infini // TODO:返回经过 cast 操作后的 shape // REF: https://onnx.ai/onnx/operators/onnx__Cast.html#cast-21 // =================================== 作业 =================================== - return std::nullopt; + // numOutputs 是 tensor 数量,不是每个tensor内的shape + // return vector{Shape(numOutputs())}; + return vector{ inputs[0]->getDims() }; } std::string CastObj::toString() const diff --git a/src/utils/operator_utils.cc b/src/utils/operator_utils.cc index edbd2c8..51efb05 100644 --- a/src/utils/operator_utils.cc +++ b/src/utils/operator_utils.cc @@ -4,13 +4,22 @@ 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 // =================================== 作业 =================================== + + Shape retShape(std::max(A.size(), B.size())); + + for (size_t i = 0; i < retShape.size(); i++) { + size_t dimA = i < A.size() ? A[A.size() - 1 - i] : 1; + size_t dimB = i < B.size() ? B[B.size() - 1 - i] : 1; + IT_ASSERT(dimA == dimB || dimA == 1 || dimB == 1) + << "mismatch dim in broadcasting"; + retShape[retShape.size() - 1 - i] = dimA != 1 ? dimA : dimB; + } - return {}; + return retShape; } int get_real_axis(const int &axis, const int &rank) {