Чистый Python
for i in range(a.shape[0]):
for j in range(b.shape[1]):
for k in range(a.shape[1]):
c[i,j] += a[i,k]*b[k,j]
Python + NumPy
import numpy as np
c = np.matmul(a,b)
C++ широко применяется в БД, движках, ИИ...
cat /procs/self/maps — посмотреть фрагменты памяти процесса self.
float** A = new float*[N];
for (size_t i = 0; i < N; ++i) { A[i]=new float[N](0.f); }
A[i][j]=1.f;
float* A = new float[N * N](0.f);
A[i*N+j]=1.f;
lscpu | grep cache или
getconf -a | grep CACHE
L1d cache: 128 KiB (4 instances)
L1i cache: 128 KiB (4 instances)
L2 cache: 1 MiB (4 instances)
L3 cache: 8 MiB (1 instance)
LEVEL1_ICACHE_LINESIZE 64
LEVEL2_DCACHE_LINESIZE 64
LEVEL3_DCACHE_LINESIZE 64
void gemm_v0(int M, int N, int K, const float * A, const float * B, float * C)
{
for (int i = 0; i < M; ++i)
{
for (int j = 0; j < N; ++j)
{
C[i*N + j] = 0;
for (int k = 0; k < K; ++k)
C[i*N + j] += A[i*K + k] * B[k*N + j];
}
}
}
Есть ли разница перемножать $\mat{A} \mat{B}$ или $\mat{A} \mat{B}^T$?
void gemm_v1(int M, int N, int K, const float * A, const float * B, float * C)
{
for (int i = 0; i < M; ++i)
{
float * c = C + i * N;
for (int j = 0; j < N; ++j)
c[j] = 0;
for (int k = 0; k < K; ++k)
{
const float * b = B + k * N;
float a = A[i*K + k];
for (int j = 0; j < N; ++j)
c[j] += a * b[j];
}
}
}
Ускорение в 8 раз (домашнее задание - убедиться в этом)
#include <immintrin.h>
С 2006~2007 года закон масштабирования Деннарда перестал выполняться
Конкурентность:
#pragma omp directive-name [опция[[,] опция]...]
#pragma omp parallel for if(N>100) numthreads(3) shared(a, b, c) private(i)
for (i = 0; i < N; i++)
c[i] = a[i] + b[i];
g++ -O2 --std=c++20 -o test -fopenmp main.cppicc -O2 --std=c++20 -o test -openmp main.cpp
24 ядра < 10000 ядер?
myKernelFunc<<<gridSize, blockSize>>>(float *param1,
int *param2) - вызов кернела
// ядро
__global__ void add( int *a, int *b, int *c ) {
*c = *a + *b;
}
...
// переменные на CPU
int a, b, c;
// переменные на GPU
int *dev_a, *dev_b, *dev_c;
int size = sizeof( int ); //размерность
// выделяем память на GPU
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
// инициализация переменных
a = 2;
b = 7;
// копирование информации с CPU на GPU
cudaMemcpy( dev_a, &a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, &b, size, cudaMemcpyHostToDevice );
// вызов ядра
add<<< 1, 1 >>>( dev_a, dev_b, dev_c );
// копирование результата работы ядра с GPU на CPU
cudaMemcpy( &c, dev_c, size, cudaMemcpyDeviceToHost );
// вывод информации
printf("%d + %d = %d\n", a, b, c);
// очищение памяти на GPU
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
__shared__ память__syncthreads() - потоки
приостанавливаются до того момента, пока все потоки не достигнут этой точки
__global__ void matrixMult(const BASE_TYPE *A, const BASE_TYPE *B, BASE_TYPE *C, int Acols, int Bcols)
{
int i0 = Acols * (blockDim.y * blockIdx.y +
threadIdx.y);
int j0 = blockDim.x * blockIdx.x + threadIdx.x;
BASE_TYPE sum = 0;
for (int k = 0; k < Acols; k++)
sum += A[i0 + k] * B[k * Bcols + j0];
int ind = Bcols * (blockDim.y * blockIdx.y +
threadIdx.y) + blockDim.x * blockIdx.x + threadIdx.x;
C[ind] = sum;
}
Пример State-of-the-art комбинации CPU/GPU в LLM: Powerinfer