2012-02-09 74 views
5

Tôi đang thực hiện một thuật toán đó, về bản chất, là một loạt các phép nhân ma trận ma trận như thế này:Làm nhiều phép nhân ma trận-ma trận trong một hoạt động

 
Res = M1.M2.M3. ... .Mn 

ma trận của tôi là nổi 100x100 thực sự nhỏ, nhưng trình tự thực sự dài, theo thứ tự của hàng tỷ.

Tôi đã thử sử dụng CUBLAS cho các phép nhân nhưng điều này chậm, tuy nhiên tôi đã chú ý điều gì đó thú vị.

nhân 100x100 với ma trận 100x100 chậm, nhưng nhân 1.000.000x100 với 100x100 tương đối nhanh, điều này khiến tôi nghĩ .Nếu thay vì quét từ trái sang phải có 10.000 lần quét song song. Điều này nên được khá nhanh, và nếu tôi nhân ma trận của tôi khi tôi đã được thực hiện với điều này, tôi sẽ nhận được kết quả tương tự - chỉ nhanh hơn.

 
Res1 = M1.M2.M3. ... .Mn/1000-1 
Res1 = M1+n/1000.M2+n/1000.M3+n/1000. ... .M2(n/1000)-1 
... 
Res1 = M1+999*n/1000.M2+999*n/1000.M3+999*n/1000. ... .M1000*(n/1000)-1 
Res = Res1*Res2* ... *Res999 

giá trị của nó không có gì mà M_1 ... M_n nằm trong một nhóm khoảng 100 ma trận khác nhau, do tiêu thụ không gian là không thực sự là một vấn đề, tất cả tôi cần đến là được làm nhiều nhân lên trong một hoạt động .

Bây giờ, đây là vấn đề của tôi. Tôi đã thực hiện một ma trận ma trận (sgemm) thực hiện lấy cảm hứng từ một nvidia thể hiện trong tài liệu của họ, nhưng nó là một thứ tự khoảng 4 lần chậm như cublas. Có ai biết CUBLAS hoạt động như thế nào không? Và nếu mã có sẵn ở đâu đó?

+0

Các ma trận có đặc biệt theo bất kỳ cách nào hữu ích không? Nếu họ đồng thời diagalizable này được đơn giản hơn rất nhiều. –

+0

@JonathanDursi: Các đặc điểm duy nhất của ma trận là cho mỗi ma trận tất cả các giá trị tổng là 1. và ma trận là bậc hai, nhưng điều đó phải rõ ràng từ mô tả. –

Trả lời

11

Bạn đã xem CUBLAS (version 4.1) mới nhất chưa? Nó bao gồm một chế độ GEMM theo đợt mới được thiết kế riêng cho các lô lớn các nhân ma trận ma trận nhỏ.Tôi sẽ đề nghị làm một cây nhân đôi như Jonathan Dursi gợi ý trong câu trả lời của mình, bằng cách sử dụng API theo lô CUBLAS để tăng tốc nó, thay vì viết kernel tùy chỉnh của riêng bạn như anh ta gợi ý.

CUBLAS 4.1 được bao gồm trong CUDA Toolkit v4.1.

CUBLAS BATCHED GEMM API IMPROVES PERFORMANCE OF BATCHES OF SMALL MATRICES

+0

Chính xác những gì tôi đang tìm kiếm. –

+2

+1 - Đó là một tính năng mới tuyệt vời, tôi đã không biết về điều đó! –

+0

Tôi tự hỏi những gì các hạn chế là để gọi này, trên macbookpro của tôi (256M nv 330M) nó chỉ đơn giản rơi vào một vòng lặp vô tận nếu tôi cố gắng chạy 10.000 112x112 phép nhân trong một đi). không có lỗi, không có gì. nhưng đối với số tiền nhỏ hơn, nó hoạt động như một nét duyên dáng :-) –

2

Vấn đề là cublas, vv được thiết kế để sử dụng tất cả các SM để nhân các ma trận lớn. Đó không phải là điều bạn muốn; bạn muốn thực hiện rất nhiều phép nhân ma trận nhỏ.

Có thể có cách nào đó để truyền nội dung này vào thứ gì đó CUBLAS có thể làm tốt cho bạn, nhưng tôi không nhìn thấy nó. Đề xuất của tôi sẽ như sau:

Viết hạt nhân sử dụng một khối chuỗi để nhân hai ma trận nhỏ của bạn và xuất kết quả.

Sau đó khởi động log kernel N với tấn khối và giải quyết những cặp nhân:

  • Bước 1: nhân M x M , M x M ... M N - 2 x M N-1, xuất ra M ', M' .. M ' N/2
  • Bước 2: nhân M' x M ', M' x M ' ... M' N/2 - 2 x M N/2-1, xuất ra M '' , M '' .. M '' N/4 ...

, vv

Sẽ có hệ số 50% phí trên bộ nhớ, nhưng tôi nghĩ bạn sẽ tận dụng tốt hơn lõi của bạn theo cách này.

Cập nhật

Ok, nếu bạn thực sự không muốn làm điều này trong giai đoạn, bạn có thể làm theo cách này nhưng nó sẽ đòi hỏi mã hóa hơn, và hiệu suất có lẽ sẽ tồi tệ hơn so với những gì bạn có thể nhận được một cái gì đó như cuBLAS và chuyển giao không đồng bộ. Tôi giả sử bạn đang sử dụng một Fermi, và bạn đã tắt bộ nhớ cache L1 để bạn có 48K chia sẻ mem cho mỗi SM.

Lưu trữ 100 ma trận ở dạng khối 2x2, với mỗi khối có liên quan trong bộ nhớ. Vì vậy, matrix[matrixnum,i,j] bắt đầu tại matricies[matrixnum*100*100 + i*100*50 + j*50*50]. Lưu ý rằng mỗi khối là 50 * 50 * 4 byte ~ 10K, do đó, 4 thoải mái phù hợp trong bộ nhớ chia sẻ.

Chỉ định mỗi chuỗi 4 chuỗi một (Nmatricies/Nblocks) chuỗi dài của ma trận để nhân lên, với một trong bốn chuỗi chịu trách nhiệm cho mỗi khối phép nhân.

Giả sử bạn đang tạo chuỗi 1 trong 4 và phần đầu tiên của ma trận bạn nhân với nhau là AxB. Bạn có trách nhiệm (1,1) của kết quả - (AB) 1,1 = A 1,1 B 1,1 + A 1,2 * B 2,1. Bạn tải trước trong A 1,1 vào khoanh vùng [0] trong bộ nhớ dùng chung.

  • tải trong myblock [1] = B 1,1 từ bộ nhớ toàn cầu
  • myblock [3] = myblock [0] * myblock [1] (ma trận mult, tất cả trong bộ nhớ chia sẻ)
  • tải trong myblock [1] = A 1,2 từ toàn cầu
  • tải trong myblock [2] = B 2,1 từ toàn cầu
  • myblock [0] = myblock [3] + (myblock [ 1] * myblock [2]) (ma trận đa và bổ sung, tất cả trong bộ nhớ chia sẻ).

Bây giờ bạn có thể lặp lại điều này cho phần còn lại của chuỗi ma trận trong phần của chuỗi, chỉ xuất hiện khi hoàn tất.

Khi bạn hoàn thành, bạn sẽ kết thúc với (#SMs) ma trận trong bộ nhớ toàn cầu, vẫn phải nhân lên, nhưng sẽ không có thêm bộ nhớ tạm thời nào trong bộ nhớ toàn cầu và bạn đã thắng ' t đã phải sao chép dữ liệu vào bộ nhớ toàn cầu khác với các ma trận gốc và danh sách của những cái để giải quyết.

Một lần nữa, không có lý do thực sự để làm điều này ngoại trừ việc bạn không thể bị làm phiền khi truyền dữ liệu đến GPU theo từng giai đoạn và hiệu suất gần như chắc chắn sẽ tồi tệ hơn; có ít bộ nhớ toàn cục hơn, nhưng bạn có thể sẽ trả tiền cho nó bằng một GEMM được cuộn bằng tay. Tin tốt là 50 không phải là bội số của 8, vì vậy bạn có thể sẽ không có quá nhiều trong cách thức xung đột ngân hàng bộ nhớ chia sẻ.

Một lần nữa, đối với điểm thưởng, bạn có thể tính toán trước tất cả các khối của tất cả các sản phẩm ma trận ghép nối trước và sau đó bằng một nửa độ dài của danh sách.

+0

Như tôi đã nói các ma trận, có kích thước khoảng 50K, là trong một bộ chỉ abou 100 ma trận khác nhau, làm cho nhu cầu bộ nhớ rất nhỏ. Những gì bạn đang phẫu thuật sẽ có nghĩa là sử dụng tuyến tính bộ nhớ đến độ dài chuỗi các ma trận của tôi, vì trình tự là dài dòng theo thứ tự tỷ tỷ này sẽ không khả thi. –

+0

Bạn vẫn sẽ có các sản phẩm trung gian; bạn có thể làm cho chúng lớn hơn hoặc nhỏ hơn bằng cách thực hiện phép nhân (ba) hoặc bốn chiều, và chỉ xử lý một tập con của các ma trận cùng một lúc, nhưng vấn đề sẽ vẫn còn. Tin tốt là @harrism đã chỉ ra rằng bạn sẽ không thực sự phải viết các hạt nhân; Tôi đã không biết về các hoạt động theo đợt mới, điều đó khá thú vị. –

+0

bạn đúng, nhưng kích thước của kết quả intermedia của tôi không cần phải lớn. Impl bạn mô tả sẽ lấy thứ gì đó như 'sizeof (Matrix) * n/2' và vì ma trận của tôi chiếm 40K và dãy là 2.000.000.000 ma trận, nó sẽ trở thành thứ 37TB trong bước đầu tiên so với thẻ của tôi. Kết quả có thể được cắt nhỏ thành các peices nhỏ hơn, nhưng vấn đề là các bản remans, rằng việc thực hiện này sẽ có một truy cập bộ nhớ og lớn. Và một sử dụng mem điên. Và có hàng loạt là tuyệt vời :-) –

-1

LIBXSMM - một thư viện nhắm mục tiêu Kiến trúc Intel cho phép nhân ma trận nhỏ, dày đặc hay thưa thớt, và nhiều nếp cuộn nhỏ được chính xác có nghĩa là để khai thác hiệu quả tốt nhất cho phép nhân ma trận nhỏ.

Ngược lại với NVidia CUBLAS (hoặc Intel MKL), LIBXSMM không dựa trên giao diện hàng loạt. Thay vào đó, người ta có thể sắp xếp cho các cuộc gọi cá nhân và cũng cung cấp "các vị trí tiếp theo" tức là, nơi các toán hạng/ma trận của các phép nhân tiếp theo được đặt (trong bộ nhớ). Ưu điểm là không cần cấu trúc dữ liệu rõ ràng hoặc định dạng chỉ mục mô tả lô.

#include <libxsmm.h> 

int main() 
{ 
    const libxsmm_gemm_prefetch_type prefetch = LIBXSMM_PREFETCH_AUTO; 
    const double alpha = 1.0, beta = 1.0; /* accumulate C-matrix */ 
    const int m = 23, n = 23, k = 23;  /* some problem size */ 
    libxsmm_dmmfunction xmm = NULL;  /* function pointer */ 

    xmm = libxsmm_dmmdispatch(23, 23, 23, /* some problem size */ 
      NULL/*lda*/, NULL/*ldb*/, NULL/*ldc*/, 
      &alpha, &beta, NULL/*flags*/, 
      NULL/*&prefetch*/); 

    if (xmm) { /* JiT'ted code has been generated */ 
# pragma omp parallel for 
    for (int i = 0; i < nbatch; ++i) { 
     const double *const ai = a + i * asize; 
     const double *const bi = b + i * bsize; 
     /* e.g., matrix C is accumulated (instead of streamed) */ 
     double *const ci = c /*+ i * csize*/; 
     /* optionally provide "next locations" */ 
     xmm(ai, bi, ci/*, 
      ai + 1 * asize, 
      bi + 1 * bsize, 
      ci + 0 * csize 
     */); 
    } 
    } 
} 

LIBXSMM sản xuất tối ưu hóa cao và mã chuyên ngành (JIT), trong đó khai thác mới nhất phần mở rộng tập lệnh (SSE3, AVX, AVX2, và AVX-512). LIBXSMM là available theo giấy phép không cho phép (mệnh đề BSD-3).

LƯU Ý: Đây không phải về CUBLAS (như được yêu cầu ban đầu).

Các vấn đề liên quan