Những loại vấn đề nào cho vay tốt cho điện toán GPU?


84

Vì vậy, tôi đã có một cái đầu tốt cho những vấn đề tôi làm việc là vấn đề tốt nhất nối tiếp và có thể được quản lý song song. Nhưng hiện tại, tôi không có nhiều ý tưởng về những gì được xử lý tốt nhất bằng tính toán dựa trên CPU và những gì nên được giảm tải cho GPU.

Tôi biết đó là một câu hỏi cơ bản, nhưng phần lớn việc tìm kiếm của tôi bị cuốn vào những người rõ ràng ủng hộ người này hay người kia mà không thực sự biện minh tại sao , hoặc quy tắc ngón tay cái hơi mơ hồ. Tìm kiếm một phản ứng hữu ích hơn ở đây.

Câu trả lời:


63

Phần cứng GPU có hai điểm mạnh đặc biệt: tính toán thô (FLOP) và băng thông bộ nhớ. Hầu hết các vấn đề tính toán khó khăn thuộc một trong hai loại này. Ví dụ, đại số tuyến tính dày đặc (A * B = C hoặc Solve [Ax = y] hoặc Dia chéoize [A], v.v.) nằm ở đâu đó trên phổ băng thông tính toán / bộ nhớ tùy thuộc vào kích thước hệ thống. Biến đổi Fourier nhanh (FFT) cũng phù hợp với khuôn này với nhu cầu băng thông tổng hợp cao. Cũng như các phép biến đổi khác, thuật toán dựa trên lưới / lưới, Monte Carlo, v.v. Nếu bạn xem các ví dụ về mã NVIDIA SDK , bạn có thể cảm nhận được các loại vấn đề thường gặp nhất.

Tôi nghĩ rằng câu trả lời mang tính hướng dẫn nhiều hơn cho câu hỏi 'Loại GPU nào thực sự tồi tệ?' Hầu hết các vấn đề không thuộc danh mục này có thể được thực hiện để chạy trên GPU, mặc dù một số vấn đề cần nhiều nỗ lực hơn các vấn đề khác.

Các vấn đề không được lập bản đồ tốt thường quá nhỏ hoặc quá khó lường. Các vấn đề rất nhỏ thiếu tính song song cần thiết để sử dụng tất cả các luồng trên GPU và / hoặc có thể phù hợp với bộ đệm cấp thấp trên CPU, giúp tăng đáng kể hiệu năng của CPU. Các sự cố không thể đoán trước có quá nhiều nhánh có ý nghĩa, có thể ngăn dữ liệu truyền trực tiếp hiệu quả từ bộ nhớ GPU đến lõi hoặc giảm sự song song bằng cách phá vỡ mô hình SIMD (xem 'các phân kỳ khác nhau '). Ví dụ về các loại vấn đề này bao gồm:

  • Hầu hết các thuật toán đồ thị (quá khó đoán, đặc biệt là trong không gian bộ nhớ)
  • Đại số tuyến tính thưa thớt (nhưng điều này cũng có hại trên CPU)
  • Sự cố xử lý tín hiệu nhỏ (ví dụ FFT nhỏ hơn 1000 điểm)
  • Tìm kiếm
  • Sắp xếp

3
Tuy nhiên, các giải pháp GPU cho những vấn đề "không thể đoán trước" này là có thể và, trong khi hiện nay không khả thi, có thể có ý nghĩa quan trọng trong tương lai.
rẽ trái

6
Tôi muốn đặc biệt thêm các nhánh vào danh sách các bộ ngắt hiệu suất GPU. Bạn muốn tất cả (hàng trăm) của bạn thực hiện cùng một hướng dẫn (như trong SIMD) để thực hiện tính toán song song thực sự. Ví dụ, trên thẻ AMD nếu bất kỳ luồng lệnh nào gặp nhánh và phải phân kỳ - tất cả các phân kỳ mặt sóng (nhóm song song). Nếu các đơn vị khác từ mặt sóng không được phân kỳ - chúng phải thực hiện lần thứ hai. Đó là ý nghĩa của maxhutch bởi khả năng dự đoán, tôi đoán vậy.
Hươu cao cổ Violet

2
@VioletGiraffe, điều đó không hẳn đúng. Trong CUDA (tức là trên GPU Nvidia), phân kỳ nhánh chỉ ảnh hưởng đến sợi dọc hiện tại, nhiều nhất là 32 luồng. Các sợi dọc khác nhau, mặc dù thực thi cùng một mã, không đồng bộ trừ khi được đồng bộ hóa rõ ràng (ví dụ với __synchtreads()).
Pedro

1
@Pedro: Đúng, nhưng việc phân nhánh nói chung không ảnh hưởng đến hiệu suất. Đối với các mã hiệu suất cao (mã GPU không phải là gì?), Việc tính đến điều đó là điều cần thiết.
jvriesem

21

Các vấn đề có cường độ số học cao và các mẫu truy cập bộ nhớ thông thường thường dễ thực hiện (ier) trên GPU và hoạt động tốt trên chúng.

Khó khăn cơ bản trong việc có mã GPU hiệu suất cao là bạn có rất nhiều lõi và bạn muốn tất cả chúng được sử dụng hết tiềm năng của chúng càng nhiều càng tốt. Các vấn đề có kiểu truy cập bộ nhớ không đều hoặc không có cường độ số học cao gây khó khăn cho việc này: hoặc bạn mất nhiều thời gian để truyền đạt kết quả hoặc bạn mất nhiều thời gian để tìm nạp nội dung từ bộ nhớ (chậm!) Và không đủ thời gian để xử lý số. Tất nhiên, tiềm năng đồng thời trong mã của bạn rất quan trọng đối với khả năng của nó cũng được triển khai tốt trên GPU.


Bạn có thể chỉ định những gì bạn có ý nghĩa của các mẫu truy cập bộ nhớ thông thường?
Fomite

1
câu trả lời của maxhutch là tốt hơn của tôi. Ý tôi là bởi mẫu truy cập thông thường là bộ nhớ được truy cập theo cách tạm thời và không gian cục bộ. Đó là: bạn không thực hiện các bước nhảy lớn xung quanh bộ nhớ nhiều lần. Đó cũng là một cái gì đó của một thỏa thuận trọn gói tôi đã nhận thấy. Nó cũng được hiểu là các mẫu truy cập dữ liệu của bạn có thể được xác định trước bởi trình biên dịch bằng cách nào đó hoặc bởi người lập trình để việc phân nhánh (các câu lệnh có điều kiện trong mã) được giảm thiểu.
Reid.Atcheson

15

Đây không phải là một câu trả lời riêng mà là một sự bổ sung cho các câu trả lời khác của maxhutchReid.Atcheson .

Để tận dụng tối đa GPU, vấn đề của bạn không chỉ cần song song cao (hoặc ồ ạt) mà còn là thuật toán cốt lõi sẽ được thực thi trên GPU, nên càng nhỏ càng tốt. Trong thuật ngữ OpenCL, điều này chủ yếu được gọi là kernel .

Nói chính xác hơn, hạt nhân phải phù hợp với thanh ghi của từng đơn vị đa xử lý (hoặc đơn vị tính toán ) của GPU. Kích thước chính xác của thanh ghi phụ thuộc vào GPU.

Do hạt nhân đủ nhỏ, dữ liệu thô của vấn đề cần phải phù hợp với bộ nhớ cục bộ của GPU (đọc: bộ nhớ cục bộ (OpenCL) hoặc bộ nhớ dùng chung (CUDA) của đơn vị tính toán). Mặt khác, ngay cả băng thông bộ nhớ cao của GPU cũng không đủ nhanh để giữ cho các thành phần xử lý luôn bận rộn.
Thông thường bộ nhớ này là khoảng 16-32 KiByte lớn .


Không phải bộ nhớ cục bộ / chia sẻ của mỗi đơn vị xử lý được chia sẻ giữa tất cả hàng chục (?) Chủ đề đang chạy trong một cụm lõi? Trong trường hợp này, bạn có thực sự cần phải giữ bộ dữ liệu làm việc của mình nhỏ hơn đáng kể để có được hiệu suất đầy đủ từ GPU không?
Dan Neely

Bộ nhớ cục bộ / chia sẻ của đơn vị xử lý chỉ có thể được truy cập bởi chính đơn vị tính toán và do đó chỉ được chia sẻ bởi các thành phần xử lý của đơn vị tính toán này. Bộ nhớ xử lý có thể truy cập bộ nhớ chung của card đồ họa (thường là 1GB). Băng thông giữa các thành phần xử lý và bộ nhớ cục bộ / chia sẻ rất nhanh (> 1TB / s) nhưng băng thông vào bộ nhớ chung chậm hơn rất nhiều (~ 100GB / giây) và cần được chia sẻ giữa tất cả các đơn vị tính toán.
Torbjorn

Tôi đã không hỏi về bộ nhớ GPU chính. Tôi nghĩ rằng bộ nhớ chết chỉ được phân bổ ở cụm cấp độ lõi không phải trên mỗi lõi riêng lẻ. ví dụ cho một nVidia GF100 / 110 gpu; cho mỗi cụm 16 SM không phải là lõi 512 cuda. Với mỗi SM được thiết kế để chạy tới 32 luồng song song tối đa hóa hiệu suất GPU sẽ yêu cầu giữ cho bộ làm việc trong phạm vi 1kb / luồng.
Dan Neely

@Torbjoern Điều bạn muốn là giữ cho tất cả các đường ống thực thi GPU luôn bận rộn, GPU đạt được hai cách sau: (1) cách phổ biến nhất là tăng chiếm dụng, hoặc nói khác đi, bằng cách tăng số lượng luồng đồng thời (hạt nhân nhỏ sử dụng ít hơn các tài nguyên được chia sẻ để bạn có thể có các chủ đề tích cực hơn); có thể tốt hơn, là (2) tăng tính song song mức hướng dẫn trong kernel của bạn, do đó bạn có thể có kernel lớn hơn với tỷ lệ chiếm tương đối thấp (số lượng chủ đề hoạt động nhỏ). Xem bit.ly/Q3KdI0
fcruz

11

Có lẽ là một bổ sung kỹ thuật hơn cho các câu trả lời trước: GPU CUDA (tức là Nvidia) có thể được mô tả như một bộ vi xử lý hoạt động tự động trên 32 luồng mỗi luồng. Các luồng trong mỗi bộ xử lý hoạt động theo bước khóa (nghĩ SIMD với các vectơ có độ dài 32).

Mặc dù cách hấp dẫn nhất để làm việc với GPU là giả vờ rằng mọi thứ hoàn toàn chạy theo bước khóa, đây không phải luôn là cách làm việc hiệu quả nhất.

Nếu mã của bạn không parallelize độc đáo / tự động đến hàng trăm / hàng ngàn chủ đề, bạn có thể phá vỡ nó xuống thành nhiệm vụ không đồng bộ cá nhân mà làm parallelize tốt, và thực hiện những chỉ với 32 đề chạy trong khóa bước. CUDA cung cấp một tập hợp các hướng dẫn nguyên tử cho phép thực hiện các mutexes , từ đó cho phép các bộ xử lý tự đồng bộ hóa với nhau và xử lý một danh sách các tác vụ trong mô hình nhóm luồng . Mã của bạn sau đó sẽ hoạt động theo cách tương tự như trên hệ thống đa lõi, chỉ cần lưu ý rằng mỗi lõi sau đó có 32 luồng của riêng nó.

Đây là một ví dụ nhỏ, sử dụng CUDA, về cách thức hoạt động của nó

/* Global index of the next available task, assume this has been set to
   zero before spawning the kernel. */
__device__ int next_task;

/* We will use this value as our mutex variable. Assume it has been set to
   zero before spawning the kernel. */
__device__ int tasks_mutex;

/* Mutex routines using atomic compare-and-set. */
__device__ inline void cuda_mutex_lock ( int *m ) {
    while ( atomicCAS( m , 0 , 1 ) != 0 );
    }
__device__ inline void cuda_mutex_unlock ( int *m ) {
    atomicExch( m , 0 );
    }

__device__ void task_do ( struct task *t ) {

    /* Do whatever needs to be done for the task t using the 32 threads of
       a single warp. */
    }

__global__ void main ( struct task *tasks , int nr_tasks ) {

    __shared__ task_id;

    /* Main task loop... */
    while ( next_task < nr_tasks ) {

        /* The first thread in this block is responsible for picking-up a task. */
        if ( threadIdx.x == 0 ) {

            /* Get a hold of the task mutex. */
            cuda_mutex_lock( &tasks_mutex );

            /* Store the next task in the shared task_id variable so that all
               threads in this warp can see it. */
            task_id = next_task;

            /* Increase the task counter. */
            next_tast += 1;

            /* Make sure those last two writes to local and global memory can
               be seen by everybody. */
            __threadfence();

            /* Unlock the task mutex. */
            cuda_mutex_unlock( &tasks_mutex );

            }

        /* As of here, all threads in this warp are back in sync, so if we
           got a valid task, perform it. */
        if ( task_id < nr_tasks )
            task_do( &tasks[ task_id ] );

        } /* main loop. */

    }

Sau đó, bạn phải gọi kernel với main<<<N,32>>>(tasks,nr_tasks)để đảm bảo rằng mỗi khối chỉ chứa 32 luồng và do đó khớp với một sợi dọc. Trong ví dụ này tôi cũng giả sử, để đơn giản, các tác vụ không có bất kỳ sự phụ thuộc nào (ví dụ: một tác vụ phụ thuộc vào kết quả của một tác vụ khác) hoặc xung đột (ví dụ: hoạt động trên cùng một bộ nhớ chung). Nếu đây là trường hợp, thì việc lựa chọn nhiệm vụ trở nên phức tạp hơn một chút, nhưng cấu trúc về cơ bản là giống nhau.

Tất nhiên, điều này phức tạp hơn so với việc chỉ làm mọi thứ trên một lô lớn, nhưng mở rộng đáng kể loại vấn đề mà GPU có thể được sử dụng.


2
Điều này đúng về mặt kỹ thuật, nhưng cần có sự song song cao để có được băng thông bộ nhớ cao và có giới hạn số lượng cuộc gọi kernel không đồng bộ (hiện tại là 16). Thee cũng là tấn hành vi không có giấy tờ liên quan đến lập kế hoạch trong bản phát hành hiện tại. Tôi khuyên bạn không nên dựa vào hạt nhân không đồng bộ để tăng hiệu suất trong thời gian này ...
Max Hutchinson

2
Những gì tôi mô tả có thể được thực hiện tất cả trong một lệnh gọi kernel. Bạn có thể tạo N khối gồm 32 luồng mỗi luồng, sao cho mỗi khối khớp với một sợi dọc. Mỗi khối sau đó có được một nhiệm vụ từ danh sách nhiệm vụ toàn cầu (kiểm soát truy cập bằng cách sử dụng nguyên tử / mutexes) và tính toán nó bằng 32 luồng bước khóa. Tất cả điều này xảy ra trong một cuộc gọi kernel. Nếu bạn muốn một ví dụ về mã, hãy cho tôi biết và tôi sẽ đăng một cái.
Pedro

4

Một điểm chưa được thực hiện cho đến nay là thế hệ GPU hiện tại không làm tốt việc tính toán điểm nổi chính xác gấp đôi như với các tính toán chính xác đơn. Nếu việc tính toán của bạn phải được thực hiện với độ chính xác gấp đôi, thì bạn có thể mong đợi thời gian chạy sẽ tăng thêm 10 lần hoặc hơn độ chính xác đơn.


Tôi muốn không đồng ý. Hầu hết (hoặc tất cả) GPU mới hơn đều có hỗ trợ chính xác gấp đôi. Hầu như mọi GPU như vậy đều báo cáo các tính toán chính xác gấp đôi chạy ở tốc độ gần bằng một nửa tốc độ của độ chính xác đơn, có thể là do việc nhân đôi đơn giản các truy cập / băng thông bộ nhớ cần thiết.
Godric Seer

1
Mặc dù sự thật là các thẻ Nvidia Tesla mới nhất và lớn nhất cung cấp hiệu suất chính xác cao gấp đôi, chỉ bằng một nửa so với hiệu suất chính xác đơn cực đại, tỷ lệ này là 8 trên 1 đối với các loại thẻ tiêu dùng kiến ​​trúc Fermi phổ biến hơn.
Brian Borchers

@GodricSeer Tỷ lệ 2: 1 của dấu phẩy động SP và DP có rất ít liên quan đến băng thông và hầu hết mọi thứ phải làm với số lượng đơn vị phần cứng tồn tại để thực hiện các hoạt động này. Thông thường sử dụng lại tệp đăng ký cho SP và DP, do đó, đơn vị dấu phẩy động có thể thực thi gấp đôi SP op dưới dạng op op. Có rất nhiều trường hợp ngoại lệ cho thiết kế này, ví dụ IBM Blue Gene / Q (không có logic SP và do đó SP chạy ở ~ 1.05x DP). Một số GPU có tỷ lệ khác 2, ví dụ 3 và 5.
Jeff

Đã bốn năm kể từ khi tôi viết câu trả lời này và tình hình hiện tại với GPU của NVIDIA là đối với các dòng GeForce và Quadro, tỷ lệ DP / SP hiện là 1/32. GPU Tesla của NVIDIA có hiệu năng chính xác gấp đôi mạnh mẽ hơn nhưng cũng có giá cao hơn rất nhiều. Mặt khác, AMD đã không làm giảm hiệu suất chính xác gấp đôi trên GPU Radeon của nó theo cùng một cách.
Brian Borchers

4

Từ quan điểm ẩn dụ, gpu có thể được nhìn thấy như một người nằm trên giường đinh. Người nằm trên cùng là dữ liệu và trong cơ sở của mỗi móng có một bộ xử lý, vì vậy móng thực sự là một mũi tên chỉ từ bộ xử lý đến bộ nhớ. Tất cả các móng tay trong một mô hình thông thường, giống như một lưới. Nếu cơ thể trải đều, cảm giác tốt (hiệu suất tốt), nếu cơ thể chỉ chạm vào một số điểm trên giường móng tay thì đau là xấu (hiệu suất kém).

Điều này có thể được coi là một câu trả lời bổ sung cho các câu trả lời xuất sắc ở trên.


4

Câu hỏi cũ, nhưng tôi nghĩ rằng câu trả lời này từ năm 2014 - liên quan đến các phương pháp thống kê, nhưng có thể khái quát cho bất kỳ ai biết vòng lặp là gì - đặc biệt mang tính minh họa và thông tin.


2

GPU có I / O có độ trễ dài, do đó, rất nhiều luồng cần được sử dụng để bão hòa bộ nhớ. Để giữ cho một sợi dọc bận rộn đòi hỏi rất nhiều chủ đề. Nếu đường dẫn mã là 10 đồng hồ và độ trễ I / O 320 đồng hồ, 32 luồng sẽ đến gần để bão hòa sợi dọc. Nếu đường dẫn mã là 5 đồng hồ, thì nhân đôi luồng.

Với một nghìn lõi, hãy tìm hàng ngàn luồng để sử dụng đầy đủ GPU.

Truy cập bộ nhớ là theo dòng bộ đệm, thường là 32 byte. Tải một byte có chi phí tương đương với 32 byte. Vì vậy, kết hợp lưu trữ để tăng địa phương sử dụng.

Có rất nhiều thanh ghi và RAM cục bộ cho mỗi sợi dọc, cho phép chia sẻ hàng xóm.

Mô phỏng gần của bộ lớn nên tối ưu hóa tốt.

I / O ngẫu nhiên và phân luồng đơn là một niềm vui giết chết ...


Đây là một câu hỏi thực sự hấp dẫn; Tôi đang tự tranh luận về việc liệu có thể (hoặc đáng nỗ lực) để 'song song' một nhiệm vụ đơn giản hợp lý (phát hiện cạnh trong ảnh trên không) khi mỗi tác vụ mất ~ 0,06 giây nhưng có ~ 1,8 triệu nhiệm vụ phải thực hiện ( mỗi năm, đối với dữ liệu có giá trị trong 6 năm: các nhiệm vụ hoàn toàn có thể tách rời) ... do đó, thời gian tính toán ~ 7,5 ngày trên một lõi. Nếu mỗi calc nhanh hơn trên GPU và công việc có thể song song 1-per-nGPUcores [n small], thì thực tế có khả năng thời gian công việc có thể giảm xuống ~ 1 giờ không? Có vẻ như không thể.
GT.

0

Hãy tưởng tượng một vấn đề có thể được giải quyết bằng rất nhiều lực lượng vũ phu, như Nhân viên bán hàng du lịch. Sau đó, hãy tưởng tượng bạn đã có rất nhiều máy chủ với 8 thẻ video cá nhân và mỗi thẻ có 3000 lõi CUDA.

Chỉ cần giải quyết TẤT CẢ các tuyến đường của nhân viên bán hàng có thể và sau đó sắp xếp thời gian / khoảng cách / một số liệu. Chắc chắn bạn đang vứt bỏ gần như 100% công việc của mình, nhưng đôi khi vũ phu là một giải pháp khả thi.


Tôi đã truy cập vào một trang trại nhỏ gồm 4 máy chủ như vậy trong một tuần và trong năm ngày tôi đã thực hiện nhiều khối phân phối.net hơn so với 10 năm trước.
Criggie

-1

Từ việc nghiên cứu nhiều ý tưởng Kỹ thuật, tôi muốn nói rằng một gpu là một hình thức tập trung của các nhiệm vụ, quản lý bộ nhớ, tính toán lặp lại.

Nhiều công thức có thể đơn giản để viết nhưng khó tính toán như trong toán học ma trận, bạn không nhận được một câu trả lời nào ngoài nhiều giá trị.

Điều này rất quan trọng trong điện toán vì máy tính tính toán các giá trị và chạy các công thức nhanh như thế nào vì một số công thức không thể chạy mà không có tất cả các giá trị được tính toán (do đó chậm lại). Một máy tính không biết rõ thứ tự nào để chạy các công thức hoặc tính toán các giá trị sẽ sử dụng trong các chương trình này. Nó chủ yếu dùng vũ lực thông qua với tốc độ nhanh và phá vỡ các công thức thành mâm cặp để tính toán, nhưng nhiều chương trình ngày nay yêu cầu các mâm tính toán này ngay bây giờ và chờ đợi trong ques (và ques ques và nhiều ques ques).

Ví dụ trong một trò chơi mô phỏng cần được tính toán trước khi va chạm thiệt hại của vụ va chạm, vị trí của các vật thể, vận tốc mới? Mất bao nhiêu thời gian Làm thế nào bất kỳ cpu có thể xử lý tải này? Ngoài ra, hầu hết các chương trình rất trừu tượng đòi hỏi nhiều thời gian hơn để xử lý dữ liệu và không phải lúc nào cũng được thiết kế cho đa luồng hoặc không có cách nào tốt trong các chương trình trừu tượng để thực hiện điều này một cách hiệu quả.

Khi cpu trở nên tốt hơn và mọi người trở nên cẩu thả hơn trong lập trình và chúng ta cũng phải lập trình cho nhiều loại máy tính khác nhau. Một gpu được thiết kế để vũ lực thông qua nhiều phép tính đơn giản cùng một lúc (không đề cập đến bộ nhớ (thứ cấp / ram) và làm mát sưởi ấm là cổ chai chính trong điện toán). Một cpu đang quản lý nhiều ques cùng một lúc hoặc bị kéo vào nhiều hướng, nó đang tìm ra những gì không thể làm được. (hey nó gần như con người)

Một gpu là công nhân lẩm cẩm công việc tẻ nhạt. Một cpu đang quản lý sự hỗn loạn hoàn toàn và không thể xử lý mọi chi tiết.

Vậy chúng ta học được gì? Một gpu thực hiện chi tiết công việc tẻ nhạt cùng một lúc và cpu là một cỗ máy đa tác vụ không thể tập trung rất tốt với quá nhiều nhiệm vụ phải làm. (Nó giống như nó bị rối loạn chú ý và tự kỷ cùng một lúc).

Kỹ thuật có ý tưởng, thiết kế, thực tế, và rất nhiều công việc khó khăn.

Khi tôi nhớ hãy bắt đầu đơn giản, bắt đầu nhanh, thất bại - nhanh chóng, thất bại nhanh chóng và không bao giờ ngừng cố gắng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.