跳转至

Programming

Cuda Program Basic

CUDA编程水平高低的不同,会导致几十上百倍的性能差距。但是这篇将聚焦于CUDA的编程语法,编译与运行。

Go Install and Command

Install

wget https://go.dev/dl/go1.18.3.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz
(maybe need sudo)
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

Command usage

$ cd $HOME/go/src/hello
$ go run main.go #直接运行
Hello, World!!
$ go build # 产生可执行文件
$ ./hello
Hello, World!!

包管理

Packages

Go packages are folders that contain one more go files.

Modules

A modules (starting with vgo and go 1.11) is a versioned collection of packages.

go get github.co­m/a­nda­nhm­/go­-pr­ett­ytimee
go mod init github.co­m/a­nda­nhm­/go­-pr­ett­ytime

go list -m -u all 来检查可以升级的package,

使用go get -u need-upgrade-package 升级后会将新的依赖版本更新到go.mod

也可以使用 go get -u 升级所有依赖

作者:若与 链接:https://www.jianshu.com/p/760c97ff644c 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://devhints.io/go

Go mod

简介

go modules 是 golang 1.11 新加的特性。现在1.12 已经发布了,是时候用起来了。Modules官方定义为:

模块是相关Go包的集合。modules是源代码交换和版本控制的单元。 go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。

使用

初始化项目

mkdir Gone
cd Gone
go mod init Gone
对应go.mod文件
module Gone
go 1.14
go.mod文件一旦创建后,它的内容将会被go toolchain全面掌控。

go toolchain会在各类命令执行时,比如go get、go build、go mod等修改和维护go.mod文件。

go.mod 提供了module, require、replace和exclude 四个命令

module 语句指定包的名字(路径) require 语句指定的依赖项模块 replace 语句可以替换依赖项模块 exclude 语句可以忽略依赖项模块

自动添加依赖

对于main.go里的import

package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "os/exec"
    "strings"
)

……
执行 go run main.go 运行代码会发现 go mod 会自动查找依赖自动下载,并修改go.mod(安装 package 的原則是先拉最新的 release tag,若无tag则拉最新的commit)

自己发布module包

结合github很简单实现

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://www.jianshu.com/p/760c97ff644c

Golang Syntax

为什么要学习go语言

  1. 同步方式轻松实现高并发,充分利用多核
  2. 基于消息传递的通信方式
  3. 适合服务器和网络编程
  4. 有垃圾回收机制
  5. 静态语言,有编译过程,和独立的静态可执行文件,只依赖glibc
  6. 不像python要安装各种库,java也要JRE
  7. 兼顾python的易开发性和c的性能
  8. 内存占用极小,支持10W+的并行

一些缺点

  1. 实际运行时,由于GC的影响,延迟会比较严重
  2. 代码会有很多重复的地方

有趣的工具

  1. gofmt
  2. gofix
  3. govet

数据类型

  • int8类型 表示 -128~127
  • Channel 类型
  • 切片类型 (可变长数组

变量声明

第一种,指定变量类型,如果没有初始化,则变量默认为零值

//var v_name v_type
var b, c int = 1, 2
//特殊
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

第二种,根据值自行判定变量类型。

//var v_name = value
var d = true

第三种,使用声明符号:=

但是如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误,格式:

v_name := value

循环语句

for key, value := range oldMap {
    newMap[key] = value
}

并发和通道通讯

go函数

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:go 函数名( 参数列表 )

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

通道(channel)

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

example

1

func countGoodRectangles(rectangles [][]int) int {
    cnt, maxLen := 0, 0
    for _, rectangle := range rectangles {
        k := int(math.Min(float64(rectangle[0]), float64(rectangle[1])))
        if k == maxLen {
            cnt++
        }
        if k > maxLen {
            maxLen, cnt = k, 1
        }
    }
    return cnt
}

webhook

https://github.com/swangeese/acsa-web/tree/webhook

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://www.runoob.com/go/go-concurrent.html

Cuda Optimize : Vectorized Memory Access

baseline

__global__ void device_copy_scalar_kernel(int* d_in, int* d_out, int N) { 
  int idx = blockIdx.x * blockDim.x + threadIdx.x; 
  for (int i = idx; i < N; i += blockDim.x * gridDim.x) { 
    d_out[i] = d_in[i]; 
  } 
} 

void device_copy_scalar(int* d_in, int* d_out, int N) 
{ 
  int threads = 128; 
  int blocks = min((N + threads-1) / threads, MAX_BLOCKS);  
  device_copy_scalar_kernel<<<blocks, threads>>>(d_in, d_out, N); 
}

简单的分块拷贝。

通过cuobjdump -sass executable.得到对应的标量copy对应的SASS代码

/*0058*/ IMAD R6.CC, R0, R9, c[0x0][0x140]                
/*0060*/ IMAD.HI.X R7, R0, R9, c[0x0][0x144]              
/*0068*/ IMAD R4.CC, R0, R9, c[0x0][0x148]               
/*0070*/ LD.E R2, [R6]                                   
/*0078*/ IMAD.HI.X R5, R0, R9, c[0x0][0x14c]              
/*0090*/ ST.E [R4], R2

(SASS不熟悉,请看SASS一文)

其中4条IMAD指令计算出读取和存储的指令地址R6:R7R4:R5。第4和6条指令执行32位的访存命令。

Vector way1: CUDA C/C++ standard headers

通过使用int2, int4, or float2

比如将int的指针d_in类型转换然后赋值。

reinterpret_cast<int2*>(d_in)
// simple in C99
(int2*(d_in))

但是需要注意对齐问题,比如

reinterpret_cast<int2*>(d_in+1)

这样是非法的。

Vector way2: structures

通过使用对齐的结构体来实现同样的目的。

struct Foo {int a, int b, double c}; // 16 bytes in size
Foo *x, *y;

x[i]=y[i];

实际修改LD.E.64

执行for循环次数减半,注意边界处理。

__global__ void device_copy_vector2_kernel(int* d_in, int* d_out, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  for (int i = idx; i < N/2; i += blockDim.x * gridDim.x) {
    reinterpret_cast<int2*>(d_out)[i] = reinterpret_cast<int2*>(d_in)[i];
  }

  // in only one thread, process final element (if there is one)
  if (idx==N/2 && N%2==1)
    d_out[N-1] = d_in[N-1];
}

void device_copy_vector2(int* d_in, int* d_out, int n) {
  threads = 128; 
  blocks = min((N/2 + threads-1) / threads, MAX_BLOCKS); 

  device_copy_vector2_kernel<<<blocks, threads>>>(d_in, d_out, N);
}

对应汇编可以看出

/*0088*/                IMAD R10.CC, R3, R5, c[0x0][0x140]              
/*0090*/                IMAD.HI.X R11, R3, R5, c[0x0][0x144]            
/*0098*/                IMAD R8.CC, R3, R5, c[0x0][0x148]             
/*00a0*/                LD.E.64 R6, [R10]                                      
/*00a8*/                IMAD.HI.X R9, R3, R5, c[0x0][0x14c]           
/*00c8*/                ST.E.64 [R8], R6

变成了LD.E.64

实际修改LD.E.128

执行for循环次数减半,注意边界处理。

__global__ void device_copy_vector4_kernel(int* d_in, int* d_out, int N) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  for(int i = idx; i < N/4; i += blockDim.x * gridDim.x) {
    reinterpret_cast<int4*>(d_out)[i] = reinterpret_cast<int4*>(d_in)[i];
  }

  // in only one thread, process final elements (if there are any)
  int remainder = N%4;
  if (idx==N/4 && remainder!=0) {
    while(remainder) {
      int idx = N - remainder--;
      d_out[idx] = d_in[idx];
    }
  }
}

void device_copy_vector4(int* d_in, int* d_out, int N) {
  int threads = 128;
  int blocks = min((N/4 + threads-1) / threads, MAX_BLOCKS);

  device_copy_vector4_kernel<<<blocks, threads>>>(d_in, d_out, N);
}

对应汇编可以看出

/*0090*/                IMAD R10.CC, R3, R13, c[0x0][0x140]              
/*0098*/                IMAD.HI.X R11, R3, R13, c[0x0][0x144]            
/*00a0*/                IMAD R8.CC, R3, R13, c[0x0][0x148]               
/*00a8*/                LD.E.128 R4, [R10]                               
/*00b0*/                IMAD.HI.X R9, R3, R13, c[0x0][0x14c]             
/*00d0*/                ST.E.128 [R8], R4

变成了LD.E.128

summary

(个人感觉,提升也不大吗?也没有两倍和四倍的效果)

绝大部分情况,向量比标量好, increase bandwidth, reduce instruction count, and reduce latency. 。

但是会增加额外的寄存器(SASS里也没有看到??)和降低并行性(什么意思???)

参考文献

https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access/#entry-content-comments

PyTorchGeometric

PyTorch Geometric Liberty

PyG是一个基于PyTorch的用于处理不规则数据(比如图)的库,或者说是一个用于在图等数据上快速实现表征学习的框架。它的运行速度很快,训练模型速度可以达到DGL(Deep Graph Library )v0.2 的40倍(数据来自论文)。除了出色的运行速度外,PyG中也集成了很多论文中提出的方法(GCN,SGC,GAT,SAGE等等)和常用数据集。因此对于复现论文来说也是相当方便。

经典的库才有函数可以支持,自己的模型,自己根据自动微分实现。还要自己写GPU并行。

MessagePassing 是网络交互的核心

数据

数据怎么存储

torch_geometric.data.Data (下面简称Data) 用于构建图

  1. 每个节点的特征 x
  2. 形状是[num_nodes, num_node_features]。
  3. 节点之间的边 edge_index
  4. 形状是 [2, num_edges]
  5. 节点的标签 y
  6. 假如有。形状是[num_nodes, *]
  7. 边的特征 edge_attr
  8. [num_edges, num_edge_features]

数据支持自定义

通过data.face来扩展Data

获取数据

在 PyG 中,我们使用的不是这种写法,而是在get()函数中根据 index 返回torch_geometric.data.Data类型的数据,在Data里包含了数据和 label。

数据处理的例子

由于是无向图,因此有 4 条边:(0 -> 1), (1 -> 0), (1 -> 2), (2 -> 1)。每个节点都有自己的特征。上面这个图可以使用 torch_geometric.data.Data来表示如下:

import torch
from torch_geometric.data import Data
# 由于是无向图,因此有 4 条边:(0 -> 1), (1 -> 0), (1 -> 2), (2 -> 1)
edge_index = torch.tensor([[0, 1, 1, 2],
                           [1, 0, 2, 1]], dtype=torch.long)
# 节点的特征                         
x = torch.tensor([[-1], [0], [1]], dtype=torch.float)

data = Data(x=x, edge_index=edge_index)

注意edge_index中边的存储方式,有两个list,第 1 个list是边的起始点,第 2 个list是边的目标节点。注意与下面的存储方式的区别。

import torch
from torch_geometric.data import Data

edge_index = torch.tensor([[0, 1],
                           [1, 0],
                           [1, 2],
                           [2, 1]], dtype=torch.long)
x = torch.tensor([[-1], [0], [1]], dtype=torch.float)

data = Data(x=x, edge_index=edge_index.t().contiguous())

这种情况edge_index需要先转置然后使用contiguous()方法。关于contiguous()函数的作用,查看 PyTorch中的contiguous。

数据集

Dataset

import torch
from torch_geometric.data import InMemoryDataset


class MyOwnDataset(InMemoryDataset): # or (Dataset)
    def __init__(self, root, transform=None, pre_transform=None):
        super(MyOwnDataset, self).__init__(root, transform, pre_transform)
        self.data, self.slices = torch.load(self.processed_paths[0])

    # 返回一个包含没有处理的数据的名字的list。如果你只有一个文件,那么它返回的list将只包含一个元素。事实上,你可以返回一个空list,然后确定你的文件在后面的函数process()中。
    @property
    def raw_file_names(self):
        return ['some_file_1', 'some_file_2', ...]

    # 很像上一个函数,它返回一个包含所有处理过的数据的list。在调用process()这个函数后,通常返回的list只有一个元素,它只保存已经处理过的数据的名字。
    @property
    def processed_file_names(self):
        return ['data.pt']

    def download(self):
        pass
        # Download to `self.raw_dir`. or just pass

    # 整合你的数据成一个包含data的list。然后调用 self.collate()去计算将用DataLodadr的片段。
    def process(self):
        # Read data into huge `Data` list.
        data_list = [...]

        if self.pre_filter is not None:
            data_list [data for data in data_list if self.pre_filter(data)]

        if self.pre_transform is not None:
            data_list = [self.pre_transform(data) for data in data_list]

        data, slices = self.collate(data_list)
        torch.save((data, slices), self.processed_paths[0])

DataLoader

DataLoader 这个类允许你通过batch的方式feed数据。创建一个DotaLoader实例,可以简单的指定数据集和你期望的batch size。

loader = DataLoader(dataset, batch_size=512, shuffle=True)

DataLoader的每一次迭代都会产生一个Batch对象。它非常像Data对象。但是带有一个‘batch’属性。它指明了了对应图上的节点连接关系。因为DataLoader聚合来自不同图的的batch的x,y 和edge_index,所以GNN模型需要batch信息去知道那个节点属于哪一图。

for batch in loader:
    batch
    >>> Batch(x=[1024, 21], edge_index=[2, 1568], y=[512], batch=[1024])

MessagePassing(核心)

其中,x 表示表格节点的 embedding,e 表示边的特征,ϕ 表示 message 函数,□ 表示聚合 aggregation 函数,γ 表示 update 函数。上标表示层的 index,比如说,当 k = 1 时,x 则表示所有输入网络的图结构的数据。

为了实现这个,我们需要定义:

  1. message
  2. 定义了对于每个节点对 (xi,xj),怎样生成信息(message)。
  3. update
  4. aggregation scheme
  5. propagate(edge_index, size=None, **kwargs)
  6. 这个函数最终会按序调用 message、aggregate 和 update 函数。
  7. update(aggr_out, **kwargs)
  8. 这个函数利用聚合好的信息(message)更新每个节点的 embedding。

propagate(edge_index: Union[torch.Tensor, torch_sparse.tensor.SparseTensor], size: Optional[Tuple[int, int]] = None, **kwargs)

  1. edge_index (Tensor or SparseTensor)
  2. 输入的边的信息,定义底层图形连接/消息传递流。
  3. torch.LongTensor类型
    1. its shape must be defined as [2, num_messages], where messages from nodes in edge_index[0] are sent to nodes in edge_index[1]
  4. torch_sparse.SparseTensor类型
    1. its sparse indices (row, col) should relate to row = edge_index[1] and col = edge_index[0].
  5. 也不一定是方形节点矩阵。x=(x_N, x_M).

MessagePassing.message(...)

会根据 flow=“source_to_target”和if flow=“target_to_source”或者x_i,x_j,来区分处理的边。

x_j表示提升张量,它包含每个边的源节点特征,即每个节点的邻居。通过在变量名后添加_i或_j,可以自动提升节点特征。事实上,任何张量都可以通过这种方式转换,只要它们包含源节点或目标节点特征。

_j表示每条边的起点,_i表示每条边的终点。x_j表示的就是每条边起点的x值(也就是Feature)。如果你手动加了别的内容,那么它的_j, _i也会自动进行处理,这个自己稍微单步执行一下就知道了

在实现message的时候,节点特征会自动map到各自的source and target nodes。

aggregate(inputs: torch.Tensor, index: torch.Tensor, ptr: Optional[torch.Tensor] = None, dim_size: Optional[int] = None, aggr: Optional[str] = None) → torch.Tensor

aggregation scheme 只需要设置参数就好,“add”, “mean”, “min”, “max” and “mul” operations

MessagePassing.update(aggr_out, ...)

aggregation 输出作为第一个参数,后面的参数是 propagate()的

实现GCN 例子

\[ \mathbf{x}_i^{(k)} = \sum_{j \in \mathcal{N}(i) \cup \{ i \}} \frac{1}{\sqrt{\deg(i)} \cdot \sqrt{\deg(j)}} \cdot \left( \mathbf{\Theta}^{\top} \cdot \mathbf{x}_j^{(k-1)} \right) \]

该式子先将周围的节点与权重矩阵\theta相乘, 然后通过节点的度degree正则化,最后相加

步骤可以拆分如下

  1. 添加self-loop 到邻接矩阵(Adjacency Matrix)。
  2. 节点特征的线性变换。
  3. 计算归一化系数
  4. Normalize 节点特征。
  5. sum相邻节点的feature(“add”聚合)。

步骤1 和 2 需要在message passing 前被计算好。 3 - 5 可以torch_geometric.nn.MessagePassing 类。

添加self-loop的目的是让featrue在聚合的过程中加入当前节点自己的feature,没有self-loop聚合的就只有邻居节点的信息。

import torch
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_self_loops, degree

class GCNConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super().__init__(aggr='add')  # "Add" aggregation (Step 5).
        self.lin = torch.nn.Linear(in_channels, out_channels)

    def forward(self, x, edge_index):
        # x has shape [N, in_channels]
        # edge_index has shape [2, E]

        # Step 1: Add self-loops to the adjacency matrix.
        edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))

        # Step 2: Linearly transform node feature matrix.
        x = self.lin(x)

        # Step 3: Compute normalization.
        row, col = edge_index
        deg = degree(col, x.size(0), dtype=x.dtype)
        deg_inv_sqrt = deg.pow(-0.5)
        deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0
        norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]

        # Step 4-5: Start propagating messages.
        return self.propagate(edge_index, x=x, norm=norm)

    def message(self, x_j, norm):
        # x_j has shape [E, out_channels]

        # Step 4: Normalize node features.
        return norm.view(-1, 1) * x_j

所有的逻辑代码都在forward()里面,当我们调用propagate()函数之后,它将会在内部调用message()和update()。

使用 GCN 的例子

conv = GCNConv(16, 32)
x = conv(x, edge_index)

SAGE的例子

聚合函数(aggregation)我们用最大池化(max pooling),这样上述公示中的 AGGREGATE 可以写为: 上述公式中,对于每个邻居节点,都和一个 weighted matrix 相乘,并且加上一个 bias,传给一个激活函数。相关代码如下(对应第二个图):

class SAGEConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super(SAGEConv, self).__init__(aggr='max')
        self.lin = torch.nn.Linear(in_channels, out_channels)
        self.act = torch.nn.ReLU()

    def message(self, x_j):
        # x_j has shape [E, in_channels]

        x_j = self.lin(x_j)
        x_j = self.act(x_j)

        return x_j

对于 update 方法,我们需要聚合更新每个节点的 embedding,然后加上权重矩阵和偏置(对应第一个图第二行):

class SAGEConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        self.update_lin = torch.nn.Linear(in_channels + out_channels, in_channels, bias=False)
        self.update_act = torch.nn.ReLU()

    def update(self, aggr_out, x):
        # aggr_out has shape [N, out_channels]

        new_embedding = torch.cat([aggr_out, x], dim=1)
        new_embedding = self.update_lin(new_embedding)
        new_embedding = torch.update_act(new_embedding)

        return new_embedding

综上所述,SageConv 层的定于方法如下:

import torch
from torch.nn import Sequential as Seq, Linear, ReLU
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import remove_self_loops, add_self_loops
class SAGEConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super(SAGEConv, self).__init__(aggr='max') #  "Max" aggregation.
        self.lin = torch.nn.Linear(in_channels, out_channels)
        self.act = torch.nn.ReLU()
        self.update_lin = torch.nn.Linear(in_channels + out_channels, in_channels, bias=False)
        self.update_act = torch.nn.ReLU()

    def forward(self, x, edge_index):
        # x has shape [N, in_channels]
        # edge_index has shape [2, E]

        # Removes every self-loop in the graph given by edge_index, so that (i,i)∉E for every i ∈ V.
        edge_index, _ = remove_self_loops(edge_index)
        # Adds a self-loop (i,i)∈ E to every node i ∈ V in the graph given by edge_index
        edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))


        return self.propagate(edge_index, size=(x.size(0), x.size(0)), x=x)

    def message(self, x_j):
        # x_j has shape [E, in_channels]

        x_j = self.lin(x_j)
        x_j = self.act(x_j)

        return x_j

    def update(self, aggr_out, x):
        # aggr_out has shape [N, out_channels]


        new_embedding = torch.cat([aggr_out, x], dim=1)

        new_embedding = self.update_lin(new_embedding)
        new_embedding = self.update_act(new_embedding)

        return new_embedding

batch的实现

GNN的batch实现和传统的有区别。

zzq的观点

将网络复制batch次,batchSize的数据产生batchSize个Loss。通过Sum或者Max处理Loss,整体同时更新所有的网络参数。至于网络中循环输入和输出的H(t-1)和Ht。(感觉直接平均就行了。

有几个可能的问题 1. 网络中参数不是线性层,CNN这种的网络。pytorch会自动并行吗?还需要手动 2. 还有个问题,如果你还想用PyG的X和edge。并不能额外拓展维度。

图像和语言处理领域的传统基本思路:

通过 rescaling or padding(填充) 将相同大小的网络复制,来实现新添加维度。而新添加维度的大小就是batch_size。

但是由于图神经网络的特殊性:边和节点的表示。传统的方法要么不可行,要么会有数据的重复表示产生的大量内存消耗。

ADVANCED MINI-BATCHING in PyG

为此引入了ADVANCED MINI-BATCHING来实现对大量数据的并行。

https://pytorch-geometric.readthedocs.io/en/latest/notes/batching.html

实现:

  1. 邻接矩阵以对角线的方式堆叠(创建包含多个孤立子图的巨大图)
  2. 节点和目标特征只是在节点维度中串联???

优势

  1. 依赖message passing 方案的GNN operators不需要修改,因为消息仍然不能在属于不同图的两个节点之间交换。
  2. 没有计算或内存开销。例如,此batching 过程完全可以在不填充节点或边特征的情况下工作。请注意,邻接矩阵没有额外的内存开销,因为它们以稀疏方式保存,只保存非零项,即边。

torch_geometric.loader.DataLoader

可以实现将多个图batch成一个大图。 通过重写collate()来实现,并继承了pytorch的所有参数,比如num_workers.

在合并的时候,除开edge_index [2, num_edges]通过增加第二维度。其余(节点)都是增加第一维度的个数。

最重要的作用

# 原本是[2*4]
# 自己实现的话,是直接连接
 >>> tensor([[0, 0, 1, 1, 0, 0, 1, 1],
             [0, 1, 1, 2, 0, 1, 1, 2]])
# 会修改成新的边
 print(batch.edge_index)
 >>> tensor([[0, 0, 1, 1, 2, 2, 3, 3],
             [0, 1, 1, 2, 3, 4, 4, 5]])

torch_geometric.loader.DataLoader 例子1

from torch_geometric.data import Data
from torch_geometric.loader import DataLoader

data_list = [Data(...), ..., Data(...)]
loader = DataLoader(data_list, batch_size=32)

torch_geometric.loader.DataLoader 例子2

from torch_geometric.datasets import TUDataset
from torch_geometric.loader import DataLoader

dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES', use_node_attr=True)
loader = DataLoader(dataset, batch_size=32, shuffle=True)

for batch in loader:
    batch
    >>> DataBatch(batch=[1082], edge_index=[2, 4066], x=[1082, 21], y=[32])

    batch.num_graphs
    >>> 32

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

Rust

简介

Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。

Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。

安装

异常简单,默认安装在自己.local/bin下,会自动修改bashrc/zshrc On Linux and macOS systems, this is done as follows:

curl https://sh.rustup.rs -sSf | sh

基础语法

printf

impl ClassName {
    pub fn printFunc() {
        let a = 12;
        println!("a is {0}, a again is {0}", a); 
        //println 不是一个函数,而是一个宏规则。所以有感叹号
    }
}

变量

Rust 是强类型语言,但具有自动判断变量类型的能力。

//可以指定类型
let a: u64 = 123;
//不可变变量
let a = 123;
let a = 456; //不是复制是,重新绑定
let s2 = s1.clone(); //这才是真复制
//变量
let mut a = 123;
a = 456;
//常量
const a: i32 = 123;

函数

函数返回值

Rust 函数声明返回值类型的方式:在参数声明之后用 -> 来声明函数返回值的类型(不是 : )。

不写return是将最后一个当作返回值?(貌似是

Rust是如何实现内存安全的呢?

内存安全

  1. buffer overflow
  2. null pointer dereference
  3. use after free
  4. use of uninitialized memory
  5. illegal free (of an already-freed pointer, or a non-malloced pointer)

所有权

所有权对大多数开发者而言是一个新颖的概念,它是 Rust 语言为高效使用内存而设计的语法机制。所有权概念是为了让 Rust 在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。

所有权三规则
  1. Rust 中的每个值都有一个变量,称为其所有者。
  2. 一次只能有一个所有者。
  3. 当所有者不在程序运行范围时,该值将被删除。
原理

如果我们定义了一个变量并给它赋予一个值,这个变量的值存在于内存中。这种情况很普遍。但如果我们需要储存的数据长度不确定(比如用户输入的一串字符串),我们就无法在定义时明确数据长度,也就无法在编译阶段令程序分配固定长度的内存空间供数据储存使用。(有人说分配尽可能大的空间可以解决问题,但这个方法很不文明)。这就需要提供一种在程序运行时程序自己申请使用内存的机制——堆。本章所讲的所有"内存资源"都指的是堆所占用的内存空间。

有分配就有释放,程序不能一直占用某个内存资源。因此决定资源是否浪费的关键因素就是资源有没有及时的释放。

我们把字符串样例程序用 C 语言等价编写:

{
    char *s = (char *)malloc(sizeof(char)*10);
    s = "nhooo"; //伪代码了
    free(s); // 释放 s 资源
}

很显然,Rust 中没有调用 free 函数来释放字符串 s 的资源(假设 "nhooo" 在堆中,这里)。Rust 之所以没有明示释放的步骤是因为在变量范围结束的时候,Rust 编译器自动添加了调用释放资源函数的步骤

这种机制看似很简单了:它不过是帮助程序员在适当的地方添加了一个释放资源的函数调用而已。但这种简单的机制可以有效地解决一个史上最令程序员头疼的编程问题。

https://hashrust.com/blog/memory-safey-in-rust-part-1/

https://deathking.github.io/2020/08/03/blue-team-rust-what-is-memory-safety-really/

https://segmentfault.com/a/1190000041151698

https://bbs.huaweicloud.com/blogs/193974

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献