Skip to main content

· 12 min read

背景

实验室开通了一项计算服务,其登录节点采用 ssh 的方式登录,但是密码是采用 “固定密码+动态口令” 形式组合构成的。 主要的问题是,这个动态口令太不方便了。 它是一个动态口令卡,一般一个人拿了该口令卡,其他人的登录的时候就麻烦得很:

  1. 每次登录必须输入密码和口令;
  2. 如果自己不持有口令卡,得向其他人询问动态口令。

    注意,我们是多人共享一个账号(即linux 用户),所有人都用这个 linux 用户操作。

现在就想能否在保证一定安全性的前提下,减少这些麻烦。 说到这,必须得继续吐槽下,这个实体的口令卡确实不像 google 验证器这类的软件那么方便,如果采用 google 验证器, 也就没有本文的这些 “旁门左道”了。

第一次尝试:ssh 多路复用

所谓 ssh 多路复用,它可以让你 ssh 连接了远程服务器后,第二次及以后的连接就可以复用第一次的连接。 这样带来的好处是,我们可以仅在第一次连接的时候输入密码+口令,以后的连接就不用输入了。

当然,这里还有一个小问题。如果每人都在自己的电脑上配置多路复用,只能解决以上的问题1,而不能解决问题2。 为此,得让多路复用的连接共享起来。 于是,想起实验室的计算集群可以完成这个任务,然后就拿这个开始了配置(有管理员权限就是不一样)。 为了共享 ssh 连接,我们先在计算集群创建一个公用的用户,就叫 pub_test 吧。 然后修改 pub_test 用户的 ssh 配置。

· 8 min read

最近在用 HIP 写 SpMV(稀疏矩阵向量乘),在算法实现过程中,遇到了一些 wavefront/block 内线程通信的问题,在此记录下。

在条件语句中谨慎使用 __syncthreads

我们都知道 __syncthreads() 可用于让 block 内的线程同步。
在 AMD GPU 上(ROCm),__syncthreads 会被编译成 s_barrier 指令(注:链接中的地址是 AMD GPU GFX9的内容),并加上必要的的全局访存(global memory) 和 LDS 访存 (shared memory) 的同步。

一般地,但 block 中的线程都会操作 LDS(如往其中写入数据), 但后续执行过程中,线程又需要用到 LDS 中的数据时(如从其中取数据),通常会在用数据之前加上 __syncthreads, 以保证前面block 内所有的线程操作 LDS 的步骤都已经完成了。

我们考虑下面这个示例代码:

#include <stdio.h>

constexpr int THREADS_PER_BLOCK = 256;
constexpr int N = 8;
constexpr int VECTOR_SIZE = 4;
constexpr int REDUCE_SIZE = 8;

__global__ void test_kernel(int *x, int *y, int alpha) {
const int g_tid = threadIdx.x + blockDim.x * blockIdx.x; // global thread id

const int g_bid = blockIdx.x; // global block id
const int tid_in_block = g_tid % blockDim.x;

__shared__ int SH[THREADS_PER_BLOCK];

constexpr int VECTOR_NUM = THREADS_PER_BLOCK / VECTOR_SIZE; // vectors in block
const int g_vector_id = g_tid / VECTOR_SIZE;
const int tid_in_vector = g_tid % VECTOR_SIZE;
const int vec_id_in_block = tid_in_block / VECTOR_SIZE;
__shared__ int lds_y[VECTOR_NUM];

int K = 0;
for (int i = 0; i < N; i++) {
const int index = i * THREADS_PER_BLOCK + g_tid;
SH[tid_in_block] = x[index];
__syncthreads(); // label:sync1:

// reduce in vector
if (vec_id_in_block < THREADS_PER_BLOCK / REDUCE_SIZE) { // label1:
int sum = 0;
for (int j = 0; j < REDUCE_SIZE / VECTOR_SIZE; j++) { // label2:
const int lds_index = vec_id_in_block * REDUCE_SIZE + tid_in_vector + j * VECTOR_SIZE;
sum += SH[lds_index];
}
for (int j = VECTOR_SIZE >> 1; j > 0; j >>= 1) {
sum += __shfl_down(sum, j, VECTOR_SIZE);
}
// store sum value to y with memory coalescing
if (tid_in_vector == 0) { // label3:
lds_y[vec_id_in_block] = sum;
}
// }
__syncthreads(); // label:sync2:
// if (vec_id_in_block < THREADS_PER_BLOCK / REDUCE_SIZE) {
if (tid_in_block < THREADS_PER_BLOCK / REDUCE_SIZE) { // label4:
const int local_sum = lds_y[tid_in_block];
y[K + tid_in_block] = alpha * local_sum;
}
}
K += THREADS_PER_BLOCK / REDUCE_SIZE;
}
}

int main() {
constexpr int DATA_SIZE = THREADS_PER_BLOCK * N;
int *hx = new int[DATA_SIZE];
int *hy = new int[DATA_SIZE/REDUCE_SIZE];
for (int i = 0; i < DATA_SIZE; i++) {
hx[i] = i;
}

int *x = nullptr;
int *y = nullptr;
cudaMalloc(&x, DATA_SIZE * sizeof(int));
cudaMalloc(&y, DATA_SIZE / REDUCE_SIZE * sizeof(int));
cudaMemcpy(x, hx, DATA_SIZE * sizeof(int), cudaMemcpyHostToDevice);

test_kernel<<<1, THREADS_PER_BLOCK>>>(x, y, 1);
cudaDeviceSynchronize();
cudaMemcpy(hy, y, DATA_SIZE / REDUCE_SIZE * sizeof(int), cudaMemcpyDeviceToHost);

for (int i = 0; i < DATA_SIZE / REDUCE_SIZE; i++) {
// let R <- REDUCE_SIZE;
// hy[i] shoule be: R*(2*R*i+R-1)/2
int R = REDUCE_SIZE;
printf("%d\n", hy[i] == (R * (2 * R * i + R - 1) / 2));
}
}

· 30 min read

北京已经开始有冬天的寒意了,据说某些地方已经开始小雪了。 想起来,还是写点东西吧,以免以后忘了,算也是对这半年的工作做一个恰当的纪录吧。

(零)

先导杯到今年已经是第二届了,不过去年我们没有参赛,所以这次还是第一次参加。 当去年那时候,看别人拿奖、屠榜,说不眼馋是假的,作为一个技术宅,谁不想炫一下自己的技术呢? 而直接导致我想参加今年的赛事主要还是,因为受到昆山超算的一位工作人员的“鼓动”,让关注下第二届先导杯。