• C++简易Tensor实现


    C++简易Tensor (更新中,未完…)

    基础知识铺垫

    缺省参数

    异常处理

    如果有模板元编程经验更好

    std::memset、std::fill、std::fill_n、std::memcpy

    std::memset 的内存填充单位固定为字节(char),所以不能应用与double,非char类型只适合置0。
    std::fill 和 std::fill_n 则可以对指定类型进行内存填充,更加通用。
    std::memcpy 则可以讲内存中排列好的数据拷贝过去,不同位置可填充不同值。

    double dp[505];
    std::memset(dp, -1.0, 505 * sizeof(double));//错误的 ★★★,memset的单位是字节(char),我们需要的是fill
    
    • 1
    • 2
    double dp[505];
    std::fill(dp, dp + 505, -1.0);
    std::fill(std::begin(dp), std::end(dp), -1.0);
    std::fill_n(dp, 505, -1.0);
    
    • 1
    • 2
    • 3
    • 4
    double dp[505];
    double data[5] = {11,22,33,44,55};
    std::memcpy(dp, data, 5 * sizeof(double))
    
    • 1
    • 2
    • 3

    一、内存管理 allocate

    在c++11中引入了智能指针这个概念,这个非常好,但是有一个问题显然被忘记了,如何动态创建智能指针数组,在c++11中没有提供直接的函数。换句话说,创建智能指针的make_shared,不支持创建数组。那在c++11中如何创建一个智能指针数组呢?只能自己封装或者变通实现,在c++14后可以支持构造函数创建智能指针数组,可这仍然不太符合技术规范发展的一致性,可继承性。

    共享指针share_ptr 和 唯一指针unique_ptr 可能并不是一个很完整的方式,因为默认情况下需要开发人员手动的指定 delete handler。 但是只需要简单的封装一下就可以是更智能的方式,就是自动生成 delete handler。并且不必使用new(或者其他的指针形式)作为构造参数,而是直接通过 allocate 和 construct 两种形式,最抽象简单直观的方式得到想要的。

    shared_ptr<T> pt0(new T());// 将会自动采用 std::default_delete
    
    shared_ptr<int> p1 = make_shared<int>();
    
    //指定 default_delete 作为释放规则
    std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
    
    //自定义释放规则
    void deleteInt(int*p) { delete []p; }
    std::shared_ptr<int> p3(new int[10], deleteInt);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们期待的规范后是这样使用的:不用考虑 释放规则,而且分为 allocate 和 construct 两种形式。

    auto uptr = Alloc::unique_allocate<Foo>(sizeof(Foo));
    auto sptr = Alloc::shared_allocate<Foo>(sizeof(Foo));
    auto uptr = Alloc::unique_construct<Foo>();
    auto sptr = Alloc::shared_construct<Foo>('6', '7');
    
    • 1
    • 2
    • 3
    • 4

    allocator.h

    #ifndef UTILS_ALLOCATOR_H
    #define UTILS_ALLOCATOR_H
    
    #include 
    #include 
    #include 
    #include 
    
    #include "base_config.h"
    
    namespace st {
    
    // 工具类(单例)
    class Alloc {
    public:
        // allocate 删除器
        class trivial_delete_handler {
        public:
            trivial_delete_handler(index_t size_) : size(size_) {}
            void operator()(void* ptr) { deallocate(ptr, size); }
        private:
            index_t size;
        };
    
        // construct 删除器
        template<typename T>
        class nontrivial_delete_handler {
        public:
            void operator()(void* ptr) {
                static_cast<T*>(ptr)->~T();
                deallocate(ptr, sizeof(T));
            }
        };
    
        // unique_ptr :对应 allocate
        template<typename T> 
        using TrivialUniquePtr = std::unique_ptr<T, trivial_delete_handler>;
    
        // unique_ptr :对应 construct
        template<typename T>
        using NontrivialUniquePtr = std::unique_ptr<T, nontrivial_delete_handler<T>>;
    
        // I know it's weird here. The type has been already passed in as T, but the
        // function parameter still need the number of bytes, instead of objects.
        // And their relationship is 
        //          nbytes = nobjects * sizeof(T).
        // Check what I do in "tensor/storage.cpp", and you'll understand.
        // Or maybe changing the parameter here and doing some extra work in 
        // "tensor/storage.cpp" is better.
    
        // 共享指针 allocate
        // 目的:自动生成 delete handler
        template<typename T> 
        static std::shared_ptr<T> shared_allocate(index_t nbytes) {
            void* raw_ptr = allocate(nbytes);
            return std::shared_ptr<T>(
                static_cast<T*>(raw_ptr),
                trivial_delete_handler(nbytes)
            );
        }
    
        // 唯一指针 allocate
        // 目的:自动生成 delete handler
        template<typename T>
        static TrivialUniquePtr<T> unique_allocate(index_t nbytes) {
            //开辟 内存
            void* raw_ptr = allocate(nbytes); 
            //返回 unique_ptr(自动生成了删除器)
            return TrivialUniquePtr<T>(
                static_cast<T*>(raw_ptr),
                trivial_delete_handler(nbytes)
            );
        }
    
        // 共享指针 construct
        // 目的:自动生成 delete handler
        template<typename T, typename... Args>
        static std::shared_ptr<T> shared_construct(Args&&... args) {
            void* raw_ptr = allocate(sizeof(T));
            new(raw_ptr) T(std::forward<Args>(args)...); 
            return std::shared_ptr<T>(
                static_cast<T*>(raw_ptr),
                nontrivial_delete_handler<T>()
            );
        }
    
        // 唯一指针 construct
        // 目的:自动生成 delete handler
        template<typename T, typename... Args>
        static NontrivialUniquePtr<T> unique_construct(Args&&... args) {
            void* raw_ptr = allocate(sizeof(T));
            new(raw_ptr) T(std::forward<Args>(args)...);
            return NontrivialUniquePtr<T>(
                static_cast<T*>(raw_ptr),
                nontrivial_delete_handler<T>()
            );
        }
    
        static bool all_clear(void);
    
    private:
        Alloc() = default;
        ~Alloc(){ 
            /* release unique ptr, the map will not do destruction!!! */
            for (auto iter = cache_.begin(); iter != cache_.end(); ++iter) {  iter->second.release(); }
        }
        static Alloc& self(); // 单例
    
        static void* allocate(index_t size);
        static void deallocate(void* ptr, index_t size);
    
        static index_t allocate_memory_size;
        static index_t deallocate_memory_size;
    
        struct free_deletor {
            void operator()(void* ptr) { std::free(ptr); }
        };
    
        // multimap 允许容器有重复的 key 值
        // 保留开辟过又释放掉的堆内存,再次使用的时候可重复使用(省略了查找可用堆内存的操作)
        std::multimap<index_t, std::unique_ptr<void, free_deletor>> cache_;
    };
    
    } // namespace st
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    allocator.cpp

    #include "allocator.h"
    #include "exception.h"
    
    #include 
    
    namespace st {
    
    index_t Alloc::allocate_memory_size = 0;
    index_t Alloc::deallocate_memory_size = 0;
    
    Alloc& Alloc::self() {
        static Alloc alloc;
        return alloc;
    }
    
    void* Alloc::allocate(index_t size) {
    
        auto iter = self().cache_.find(size);
    
        void* res;
        if(iter != self().cache_.end()) {
            // 临时:为什么要这么做?找到了为社么要删除
            res = iter->second.release();//释放指针指向内存
            self().cache_.erase(iter);//擦除
        } else {
            res = std::malloc(size); 
            CHECK_NOT_NULL(res, "failed to allocate %d memory.", size);
        }
        allocate_memory_size += size;
        return res;
    }
    
    void Alloc::deallocate(void* ptr, index_t size) {
        deallocate_memory_size += size;
    
        // 本质上是保留保留 堆内存中的位置,下一次可直接使用,而不是重新开辟
        self().cache_.emplace(size, ptr); // 插入
    }
    
    bool Alloc::all_clear() {
        return allocate_memory_size == deallocate_memory_size;
    }
    
    } // namespace st
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    使用:封装成 unique_allocate、unique_construct、share_allocate、share_construct 的目的就是对 share_ptr 和 unique_ptr 的生成自动赋予其对应的 delete handler。

    struct Foo {
        static int ctr_call_counter;
        static int dectr_call_counter;
    
        char x_;
        char y_;
        Foo() { ++ctr_call_counter; }
        Foo(char x, char y) : x_(x), y_(y) { ++ctr_call_counter; }
        ~Foo() { ++dectr_call_counter; }
    };
    int Foo::ctr_call_counter = 0;
    int Foo::dectr_call_counter = 0;
    
    void test_Alloc() {
        using namespace st;
    
        // allocate 开辟空间
        // construct 开辟空间 + 赋值
    
        void* ptr;
        
        {//
            auto uptr = Alloc::unique_allocate<Foo>(sizeof(Foo));
            CHECK_EQUAL(Foo::ctr_call_counter, 0, "check 1");
            ptr = uptr.get();
        }
        CHECK_EQUAL(Foo::dectr_call_counter, 0, "check 1");
    
        {
            auto sptr = Alloc::shared_allocate<Foo>(sizeof(Foo));
            // The strategy of allocator.
            CHECK_EQUAL(ptr, static_cast<void*>(sptr.get()), "check 2");
        }
    
        {
            auto uptr = Alloc::unique_construct<Foo>();
            CHECK_EQUAL(Foo::ctr_call_counter, 1, "check 3");
            CHECK_EQUAL(ptr, static_cast<void*>(uptr.get()), "check 3");
        }
        CHECK_EQUAL(Foo::dectr_call_counter, 1, "check 3");
    
        {
            auto sptr = Alloc::shared_construct<Foo>('6', '7');
            CHECK_EQUAL(Foo::ctr_call_counter, 2, "check 4");
            CHECK_TRUE(sptr->x_ == '6' && sptr->y_ == '7', "check 4");
            CHECK_EQUAL(ptr, static_cast<void*>(sptr.get()), "check 4");
        }
        CHECK_EQUAL(Foo::dectr_call_counter, 2, "check 4");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    实现 Tensor 需要准备 shape 和 storage

    shape 管理形状,每一个Tensor的形状都是唯一的(采用 unique_ptr管理数据),见array.h 个 shape.h。
    storage:管理数据,不同的Tensor的数据可能是同一份数据(share_ptr管理数据),见stroage.h。

    array.h

    #ifndef UTILS_ARRAY_H
    #define UTILS_ARRAY_H
    
    #include 
    #include 
    #include 
    #include 
    
    // utils
    #include "base_config.h"
    #include "allocator.h"
    
    namespace st {
    
    	// 应用是 tensor 的 shape, shape 是唯一的, 所以用 unique_ptr
    	// 临时:实际上并不是很完善,目前的样子有点对不起这个 Dynamic 单词
    	template<typename Dtype>
    	class DynamicArray {
    	public:
    	    explicit DynamicArray(index_t size) 
    	            : size_(size),
    	              dptr_(Alloc::unique_allocate<Dtype>(size_ * sizeof(Dtype))) {
    	    }
    	
    	    DynamicArray(std::initializer_list<Dtype> data) 
    	            : DynamicArray(data.size()) {
    	        auto ptr = dptr_.get();
    	        for(auto d: data) {
    	            *ptr = d;
    	            ++ptr;
    	        }
    	    }
    	
    	    DynamicArray(const DynamicArray<Dtype>& other) 
    	            : DynamicArray(other.size()) {
    	        std::memcpy(dptr_.get(), other.dptr_.get(), size_ * sizeof(Dtype));
    	    }
    	
    	    DynamicArray(const Dtype* data, index_t size) 
    	            : DynamicArray(size) {
    	        std::memcpy(dptr_.get(), data, size_ * sizeof(Dtype));
    	    }
    	
    	    explicit DynamicArray(DynamicArray<Dtype>&& other) = default;
    	
    	    ~DynamicArray() = default;
    	
    	    Dtype& operator[](index_t idx) { return dptr_.get()[idx]; }
    	    Dtype operator[](index_t idx) const { return dptr_.get()[idx]; }
    	    index_t size() const { return size_; }
    	
    	    // 注意 std::memset 的单位是字节(char),若不是char类型,只用来置0,否则结果错误
    	    // 临时:std::memset 对非char类型只适合内存置0,如果想要更加通用,不妨考虑一下 std::fill 和 std::fill_n
    	    void memset(int value) const { std::memset(dptr_.get(), value, size_ * sizeof(Dtype)); } //原
    	    void fill(int value) const (std::fill_n, size_, value); //改:见名知意
    	
    	private:
    	    index_t size_;
    	    Alloc::TrivialUniquePtr<Dtype> dptr_;
    	};
    	
    } // namespace st
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    stroage.h

    #ifndef TENSOR_STORAGE_H
    #define TENSOR_STORAGE_H
    
    #include 
    
    #include "base_config.h"
    #include "allocator.h"
    
    namespace st {
    
        namespace nn {
            class InitializerBase;
            class OptimizerBase;
        }
    
        class Storage {
        public:
            explicit Storage(index_t size);
            Storage(const Storage& other, index_t offset); //观察:offset 具体应用?bptr_数据依然是同一份,只是dptr_指向位置不同,这是关于pytorch的clip,切片等操作的设计方法
            Storage(index_t size, data_t value);
            Storage(const data_t* data, index_t size);
    
            explicit Storage(const Storage& other) = default;//复制构造(因为数据都是指针形式,所以直接默认就行)
            explicit Storage(Storage&& other) = default;//移动构造(因为数据都是指针形式,所以直接默认就行)
            ~Storage() = default;
            Storage& operator=(const Storage& other) = delete;
    
            // inline function
            data_t operator[](index_t idx) const { return dptr_[idx]; }
            data_t& operator[](index_t idx) { return dptr_[idx]; }
            index_t offset(void) const { return dptr_ - bptr_->data_; }//
            index_t version(void) const { return bptr_->version_; }//
            void increment_version(void) const { ++bptr_->version_; }//???
    
            // friend function
            friend class nn::InitializerBase;
            friend class nn::OptimizerBase;
        public:
            index_t size_;
        private:
            struct Vdata {
                index_t version_; //???
                data_t data_[1]; //永远指向数据头
            };
    
            std::shared_ptr<Vdata> bptr_;  // base pointer, share_ptr 的原因是不同的tensor可能指向的是storage数据
            data_t* dptr_;  // data pointer, 指向 Vdata 中的 data_, 他是移动的(游标)
        };
    }  // namespace st
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    storage.cpp

    #include 
    #include 
    #include 
    
    #include "storage.h"
    
    namespace st {
    
        Storage::Storage(index_t size)
            : bptr_(Alloc::shared_allocate<Vdata>(size * sizeof(data_t) + sizeof(index_t))),
            dptr_(bptr_->data_)
        {
            bptr_->version_ = 0;
            this->size_ = size;
        }
    
        Storage::Storage(const Storage& other, index_t offset)
            : bptr_(other.bptr_),
            dptr_(other.dptr_ + offset)
        {
            this->size_ = other.size_;
        }
    
        Storage::Storage(index_t size, data_t value)
            : Storage(size) {
            //std::memset(dptr_, value, size * sizeof(data_t)); // 临时
            std::fill_n(dptr_, size, value);
        }
    
        Storage::Storage(const data_t* data, index_t size)
            : Storage(size) {
            std::memcpy(dptr_, data, size * sizeof(data_t));
        }
    
    }  // namespace st
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    shape.h

    #ifndef TENSOR_SHAPE_H
    #define TENSOR_SHAPE_H
    
    #include 
    #include 
    
    #include "base_config.h"
    #include "allocator.h"
    #include "array.h"
    
    namespace st {
    
        class Shape {
        public:
            // constructor
            Shape(std::initializer_list<index_t> dims);
            Shape(const Shape& other, index_t skip);
            Shape(index_t* dims, index_t dim);
            Shape(IndexArray&& shape);
    
            Shape(const Shape& other) = default;
            Shape(Shape&& other) = default;
            ~Shape() = default;
    
            // method
            index_t dsize() const;
            index_t subsize(index_t start_dim, index_t end_dim) const;
            index_t subsize(index_t start_dim) const;
            bool operator==(const Shape& other) const;
    
            // inline function
            index_t ndim(void) const { return dims_.size(); }
            index_t operator[](index_t idx) const { return dims_[idx]; }
            index_t& operator[](index_t idx) { return dims_[idx]; }
            operator const IndexArray() const { return dims_; }
    
            // friend function
            friend std::ostream& operator<<(std::ostream& out, const Shape& s);
    
        private:
            IndexArray dims_; // IndexArray 就是(DynamicArray)
        };
    
    }  // namespace st
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    shape.cpp

    #include "shape.h"
    
    namespace st {
    
        Shape::Shape(std::initializer_list<index_t> dims) : dims_(dims) {}
    
        Shape::Shape(const Shape& other, index_t skip) : dims_(other.ndim() - 1) {
            int i = 0;
            for (; i < skip; ++i)
                dims_[i] = other.dims_[i];
            for (; i < dims_.size(); ++i)
                dims_[i] = other.dims_[i + 1];
        }
    
        Shape::Shape(index_t* dims, index_t dim_) : dims_(dims, dim_) {}
    
        Shape::Shape(IndexArray&& shape) : dims_(std::move(shape)) {}
    
        index_t Shape::dsize() const {
            int res = 1;
            for (int i = 0; i < dims_.size(); ++i)
                res *= dims_[i];
            return res;
        }
    
        index_t Shape::subsize(index_t start_dim, index_t end_dim) const {
            int res = 1;
            for (; start_dim < end_dim; ++start_dim)
                res *= dims_[start_dim];
            return res;
        }
    
        index_t Shape::subsize(index_t start_dim) const {
            return subsize(start_dim, dims_.size());
        }
    
        bool Shape::operator==(const Shape& other) const {
            if (this->ndim() != other.ndim()) return false;
            index_t i = 0;
            for (; i < dims_.size() && dims_[i] == other.dims_[i]; ++i)
                ;
            return i == dims_.size();
        }
    
        std::ostream& operator<<(std::ostream& out, const Shape& s) {
            out << '(' << s[0];
            for (int i = 1; i < s.ndim(); ++i)
                out << ", " << s[i];
            out << ")";
            return out;
        }
    
    }  // namespace st
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    Tensor 的设计方法(基础)

    知识准备:继承、指针类、奇异递归模板(静态多态)、表达式模板、Impl设计模式(声明实现分离)、友元类、模板特化。

    tensor的设计采用的 impl 方法(声明和实现分离), 采用了奇异递归模板(静态多态),Tensor本身管理Tensor的张量运算,Exp则管理引用计数、梯度计数(反向求导,梯度更新时需要用到)的运算。

    一共5个类:Tensor,TensorImpl,Exp,ExpImpl,ExpImplPtr,他们之间的关系由下图体现。

    先上图:
    在这里插入图片描述
    代码:

    
    // 代码比较多,就不放在这了,参看源码结合注释理解
    
    
    • 1
    • 2
    • 3

    Tensor 的设计方法(更进一步)

    Tensor 数据内存分布管理
    Tensor的数据只有独一份,那么Tensor的各种操作 transpose,purmute,slice,等等,难道都要生出一个新的 tensor 和对应新的数据吗?当然不可能,能用一份数据的绝不用两份!tensor 数据的描述主要有 size(总数数据量),offset(此 tensor 相对于原始base数据的一个偏移量) ndim(几个维度),shape(每个维度映射的个数),stride(每个维度中数据的索引步长),stride 和 shape是 一 一 对应的,通过这个stride的索引公式,我们就可以用一份数据幻化出不同的tensor表象了。解析如下图

    在这里插入图片描述
    permute(轴换位置):shape 和 stride 调换序列一致即可。
    transpose(指定两个轴换位置,转置):同上,与permute一致。
    slice(切片):在原始数据上增加了一个偏移量。Tensor中的数据部分Storage中有一个bptr_(管理原始数据)和dptr_(管理当前tensor的数据指向)。
    unsqueese(升维):指定dim加一个维度,且shape值为1,stride值则根据shape的subsize(dim)算出即可。
    squeese(降维):dim为1的将自动消失并降维,shape 和 stride 对应位子都会去掉。
    view(变形):目前是只支持连续数据分布且数据的size总和不变的情况,比如permute、transpose就会破坏这种连续。slice就会破坏数据size不一致。

  • 相关阅读:
    docker mysql主从复制
    SpringBoot学习笔记-配置MySQL与实现注册登录模块(中)
    wordpress 上传附件中文文件名乱码解决办法(for Windows)
    爬虫框架Scrapy学习笔记-2
    宝塔部署项目添加域名配置证书 ERR_TOO_MANY_REDIRECTS 重定向次数过多
    XML语法、约束
    云计算与 openstack
    〖Python 数据库开发实战 - Python与Redis交互篇④〗- 利用 redis-py 实现集合与有序集合的常用指令操作
    在Jetson Nano上安装ncnn深度学习框架
    【python】自动化工具Selenium与playwright去除webdriver检测
  • 原文地址:https://blog.csdn.net/weixin_40011280/article/details/126166678