2014-09-17 18 views
5

Dưới đây là hạt nhân opencl thực hiện phép nhân ma trận bị chặn cho nhiều ma trận độc lập. selectMatrixA và chọnMatrixB lưu trữ nhiều ma trận (cùng kích thước và ma trận vuông) theo thứ tự chính của hàng.Tối ưu hóa mã khối nhân ma trận theo lô

// Matrix multiplication: C = A * B. 


#define BLOCK_SIZE 20 
#define MATRIX_SIZE 100 * 100 

#define BLOCK_DIMX 5 // Number of blocks in the x dimension 

__kernel void 
batchedMatrixMul(__global float *selectMatrixC, __global float *selectMatrixA, __global 
float *selectMatrixB, int wA, int wB) 
{ 
    // Block index 
    int bx = get_group_id(0); 
    int by = get_group_id(1); 


    __global float *C = selectMatrixC + (bx/BLOCK_DIMX) * MATRIX_SIZE; 
    __global float *A = selectMatrixA + (bx/BLOCK_DIMX) * MATRIX_SIZE; 
    __global float *B = selectMatrixB + (bx/BLOCK_DIMX) * MATRIX_SIZE; 



    int tx = get_local_id(0); 
    int ty = get_local_id(1); 

    float Csub = 0; 

    // Identify the row and column of the C matrix to work on 

    int Row = (by * BLOCK_SIZE) + ty; 
    int Col = ((bx %(BLOCK_DIMX)) * BLOCK_SIZE) + tx; 

    // Declaration of the local memory array As used to store the sub-matrix of A 
    __local float As[BLOCK_SIZE][BLOCK_SIZE]; 

    // Declaration of the local memory array Bs used to store the sub-matrix of B 
    __local float Bs[BLOCK_SIZE][BLOCK_SIZE]; 

    // Loop over all the sub-matrices of A and B required to compute the block sub-matrix 
    for (int m = 0; m < wA/BLOCK_SIZE; ++m) 
    { 

     // Load the matrices from global memory to local memory. Each thread loads one 
     //element of each matrix 
     As[ty][tx] = A[Row * wA + m * BLOCK_SIZE + tx]; 
     Bs[ty][tx] = B[(m * BLOCK_SIZE + ty)*wA + Col]; 

     // Synchronize to make sure the matrices are loaded 
     barrier(CLK_LOCAL_MEM_FENCE); 

     // Multiply the two matrices together each thread computes one element of the block 
     //sub-matrix 
     for (int k = 0; k < BLOCK_SIZE; ++k) 
      Csub += As[ty][k] * Bs[k][tx]; 

     // Synchronize to make sure that the preceding computation is done before loading 
     //two new sub-matrices of A and B in the next iteration 
     barrier(CLK_LOCAL_MEM_FENCE); 

    } 

    // Write the block sub-matrix to device memory each thread writes one element 
    C[Row * wA + Col] = Csub; 

} 

Dưới đây là cách tôi khởi động hạt nhân:

localWorkSize[0] = BLOCK_SIZE; 
localWorkSize[1] = BLOCK_SIZE; 

// for a 100 X 100 matrix, MATRIX_DIMX = MATRIX_DIMY = 100 
globalWorkSize[0] = MATRIX_DIMX * NUM_MATRICES; 
globalWorkSize[1] = MATRIX_DIMY ; 

cl_event    event; 
errcode = clEnqueueNDRangeKernel(clCommandQueue, 
      clKernel, 2, NULL, globalWorkSize, 
      localWorkSize, 0, NULL, &event); 

Dưới đây là một số con số hiệu suất khi chạy này trên một NVIDIA Lưới K520:

1. matrix size:100 X 100 . Number of matrices = 20000. Time taken for multiplication = 
0.262 seconds. As shown in the code, the block size was set to 20. Block size of 10 was 
slower. This calculates to around 152 GFLOPS 

2. matrix size: 10000 X 10000. Number of matrices = 1. Time taken for multiplication = 10.6 
seconds. Here also the block size was 20. Using a block size of 50 is not possible due to 
the size of the local memory. 

Ai đó có thể vui lòng giúp tôi hiểu tại sao mã chạy chậm và tại sao 2. chậm hơn rất nhiều so với 1. Tôi mới dùng OpenCL và tôi muốn tìm hiểu cách tối ưu hóa mã dựa trên các chi tiết kiến ​​trúc cơ bản.

+0

bạn đã thử phương pháp tiếp cận sản phẩm chấm đơn giản để giải quyết vấn đề và đo lường hiệu suất cơ bản chưa? có thể lấy từng mục công việc để tính toán một phần tử trong C bằng cách thực hiện thao tác 100 điểm 100. – mfa

+0

kích thước ma trận của bạn có phải là 100x100 và 10k x 10k không? – mfa

+0

là một yêu cầu để giải quyết một phép toán nhân đầy đủ với một nhóm làm việc duy nhất, hoặc nó có thể được thực hiện với nhiều nhóm không? – mfa

Trả lời

1

Theo tôi lý do tại sao 2. chậm hơn rất nhiều là mẫu truy cập của phép nhân ma trận không phải là bộ nhớ cache thân thiện. Nếu bạn cần nhận giá trị đầu tiên của hàng đầu tiên và giá trị đầu tiên của hàng thứ hai, chúng được lưu trữ vào bộ nhớ rất xa nhau. Nếu kích thước ma trận tăng, chúng được lưu trữ xa nhau hơn. Điều này sẽ dẫn đến rất nhiều lần nhớ cache.

Tôi không có kinh nghiệm cá nhân về nhân ma trận, nhưng tôi chỉ nghĩ rằng có thể lưu trữ dữ liệu của bạn ở Z-order curve để có được mẫu thân thiện với bộ nhớ cache hơn. Từ các tài liệu tham khảo của Wikipedia có vẻ như một cái gì đó như thế đã được thực hiện bởi Valsalam & al 2002.

Sửa lỗi nhanh khác, tôi sẽ thử trước khi sử dụng nhiều thời gian để đặt hàng Z, là sử dụng các biến riêng tư và loại bỏ các rào cản. Ngay cả khi nó đòi hỏi nhiều tải hơn từ bộ nhớ toàn cầu, có thể trình biên dịch có thể thực hiện tối ưu hóa tốt hơn nhiều cho mã đó.

3

Lý do tại sao thử nghiệm đầu tiên của bạn nhanh hơn rất nhiều là do có sự khác biệt về số lượng công việc mà mỗi thử nghiệm đang thực hiện. Trên thực tế, một yếu tố 50x.

Big-O cho phép nhân ma trận vuông là O (n^3). Xem: why is the time complexity of square matrix multiplication defined as O(n^3)? Kết quả là ma trận bình phương 10k thực sự mất thêm 1 triệu lần công việc nhân với một phép nhân 100x100 đơn. 20000 thực thi phép nhân 100x100 của bạn không bù đắp cho số lượng lớn công việc cần thiết để nhân các ma trận lớn một lần.

Phép nhân ma trận chỉ là nhiều sản phẩm chấm. Thuật toán của bạn chỉ chia các sản phẩm chấm thành nhóm để dễ xử lý và không sử dụng bất kỳ thủ thuật đặc biệt nào để giảm số trong các tính toán của tôi bên dưới.

Đối với thử nghiệm ma trận nhỏ của bạn:

Total dot products: 10^4 
MADs per dot product: 10^2 
Total matrix-multiply operations: 20000 = 2*10^4 
Total multiply-adds: 2* 10^(4+2+4) = 2*10^10 = 20,000,000,000 

20 tỷ.

kiểm tra ma trận lớn:

Total dot products: 10^8 
MADs per dot product: 10^4 
Total multiply operations: 1 (or 10^0) 
Grand total multiply-adds: 10^(8 + 4 + 0) = 10^12 = 1,000,000,000,000 

1000 tỷ.

Kiểm tra 10000x10000 của bạn về mặt kỹ thuật đang chạy nhanh hơn - kéo dài thêm 50 lần hoạt động chỉ trong thời gian chạy thêm 40 lần.

Đọc thêm về 'các thủ thuật đặc biệt' tại đây: http://en.wikipedia.org/wiki/Strassen_algorithm.Mặc dù thuật toán này không được coi là thực tế cho tính toán GPU. Các thuật toán phức tạp của Mor cũng tồn tại, nhưng cách tiếp cận bạo lực trên phần cứng đồ họa dường như được sử dụng thường xuyên nhất.

Tại sao hạt nhân của bạn chạy chậm nói chung? Có một số tối ưu hóa khác nhau mà bạn có thể sử dụng để tăng tốc độ. Dưới đây chỉ là một vài bạn có thể google và thử nghiệm với chính mình. Có thể bạn sẽ gặp một số điều tôi chưa đề cập ở đây.

  • Tối ưu hóa nhóm làm việc và kích thước khối. xem opencl PREFERRED_WORK_GROUP_SIZE
  • Sử dụng kiểu dữ liệu float4. opencl bao gồm một chức năng sản phẩm chấm tính toán sản phẩm chấm cho các kiểu dữ liệu floatn.
  • Chuyển ma trận B trước khi chạy hạt nhân. bạn có thể sử dụng một hạt nhân khác để thực hiện chuyển vị.