Các thuật toán song song (GPU) cho máy tự động di động không đồng bộ


12

Tôi có một bộ sưu tập các mô hình tính toán có thể được mô tả là automata di động không đồng bộ. Những mô hình này giống với mô hình Ising, nhưng phức tạp hơn một chút. Có vẻ như các mô hình như vậy sẽ được hưởng lợi từ việc chạy trên GPU chứ không phải CPU. Thật không may, nó không hoàn toàn đơn giản để song song với một mô hình như vậy, và tôi không rõ ràng về cách thực hiện nó. Tôi biết rằng có tài liệu về đề tài này, nhưng dường như tất cả đều nhắm vào các nhà khoa học máy tính khó tính, những người quan tâm đến các chi tiết về độ phức tạp thuật toán, thay vì một người như tôi chỉ muốn mô tả về một cái gì đó tôi có thể thực hiện, và do đó tôi thấy nó khá khó hiểu.

Để rõ ràng, tôi không tìm kiếm một thuật toán tối ưu nhiều như một thứ tôi có thể thực hiện nhanh chóng trong CUDA có khả năng tăng tốc đáng kể so với việc triển khai CPU của tôi. Thời gian lập trình viên là một yếu tố hạn chế hơn nhiều so với thời gian máy tính trong dự án này.

Tôi cũng nên làm rõ rằng một máy tự động di động không đồng bộ là một thứ khá khác so với máy đồng bộ và các kỹ thuật để song song các CA đồng bộ (như cuộc sống của Conway) không thể dễ dàng thích nghi với vấn đề này. Sự khác biệt là một CA đồng bộ cập nhật đồng thời mọi ô ở mọi thời điểm, trong khi đó một CA không đồng bộ cập nhật một vùng cục bộ được chọn ngẫu nhiên ở mỗi bước như được nêu dưới đây.

Các mô hình tôi muốn song song được triển khai trên một mạng tinh thể (thường là một hình lục giác) bao gồm ~ 100000 ô (mặc dù tôi muốn sử dụng nhiều hơn) và thuật toán không song song để chạy chúng trông như thế này:

  1. Chọn một cặp ô lân cận ngẫu nhiên

  2. Tính toán một "năng lượng" chức năng dựa trên một khu phố địa phương xung quanh các tế bào nàyΔE

  3. Với một xác suất mà phụ thuộc vào (với β một tham số), hoặc trao đổi các trạng thái của hai tế bào hoặc không làm gì cả.e-βΔEβ

  4. Lặp lại các bước trên vô thời hạn.

Cũng có một số phức tạp liên quan đến các điều kiện biên, nhưng tôi tưởng tượng những điều này sẽ không gây khó khăn nhiều cho việc song song hóa.

Điều đáng nói là tôi quan tâm đến động lực nhất thời của các hệ thống này chứ không chỉ là trạng thái cân bằng, vì vậy tôi cần một cái gì đó có động lực tương đương với ở trên, thay vì chỉ là thứ sẽ tiếp cận phân phối cân bằng tương tự. (Vì vậy, các biến thể của thuật toán bảng điều khiển không phải là thứ tôi đang tìm kiếm.)

Khó khăn chính trong việc song song thuật toán trên là va chạm. Bởi vì tất cả các tính toán chỉ phụ thuộc vào một vùng cục bộ của mạng, nên nhiều trang mạng có thể được cập nhật song song, miễn là các vùng lân cận của chúng không bị chồng chéo. Câu hỏi là làm thế nào để tránh chồng chéo như vậy. Tôi có thể nghĩ ra một số cách, nhưng tôi không biết nếu đó là cách tốt nhất để thực hiện. Đó là như sau:

  • Sử dụng CPU để tạo danh sách các trang web lưới ngẫu nhiên và kiểm tra va chạm. Khi số lượng trang web lưới bằng số lượng bộ xử lý GPU hoặc nếu phát hiện xung đột, hãy gửi từng bộ tọa độ đến một đơn vị GPU để cập nhật trang web lưới tương ứng. Điều này sẽ dễ thực hiện nhưng có lẽ sẽ không tăng tốc nhiều, vì việc kiểm tra va chạm trên CPU có lẽ sẽ không rẻ hơn nhiều so với thực hiện toàn bộ cập nhật trên CPU.

  • Chia mạng tinh thể thành các vùng (một đơn vị cho mỗi đơn vị GPU) và có một đơn vị GPU chịu trách nhiệm chọn ngẫu nhiên và cập nhật các ô lưới trong khu vực của nó. Nhưng có nhiều vấn đề với ý tưởng này mà tôi không biết cách giải quyết, rõ ràng nhất là chính xác những gì sẽ xảy ra khi một đơn vị chọn một khu phố nằm chồng lên rìa của khu vực.

  • Xấp xỉ hệ thống như sau: hãy để thời gian tiến hành theo các bước riêng biệt. Chia lưới thành một khácthiết lập các vùng trên mỗi bước theo một sơ đồ được xác định trước và để mỗi đơn vị GPU chọn ngẫu nhiên và cập nhật một cặp ô lưới có vùng lân cận không chồng lấp ranh giới của vùng. Vì các ranh giới thay đổi mỗi lần bước này, ràng buộc này có thể không ảnh hưởng quá nhiều đến động lực, miễn là các vùng tương đối lớn. Điều này có vẻ dễ thực hiện và có khả năng nhanh, nhưng tôi không biết nó sẽ xấp xỉ động lực học như thế nào, hoặc kế hoạch tốt nhất để chọn ranh giới khu vực trên mỗi bước thời gian là gì. Tôi tìm thấy một số tài liệu tham khảo về "automata di động đồng bộ khối", có thể giống hoặc không giống như ý tưởng này. (Tôi không biết vì dường như tất cả các mô tả về phương pháp này đều bằng tiếng Nga hoặc nằm trong các nguồn mà tôi không có quyền truy cập.)

Các câu hỏi cụ thể của tôi như sau:

  • Có bất kỳ thuật toán nào ở trên là một cách hợp lý để tiếp cận song song hóa GPU của mô hình CA không đồng bộ không?

  • Có cách nào tốt hơn?

  • Có mã thư viện hiện có cho loại vấn đề này?

  • Tôi có thể tìm thấy mô tả bằng tiếng Anh rõ ràng về phương pháp "đồng bộ khối" ở đâu?

Phát triển

Tôi tin rằng tôi đã đưa ra một cách để song song một CA không đồng bộ có thể phù hợp. Thuật toán được phác thảo dưới đây dành cho một CA không đồng bộ thông thường chỉ cập nhật một ô tại một thời điểm, thay vì một cặp ô lân cận như của tôi. Có một số vấn đề với việc khái quát nó cho trường hợp cụ thể của tôi, nhưng tôi nghĩ rằng tôi có một ý tưởng làm thế nào để giải quyết chúng. Tuy nhiên, tôi không chắc nó sẽ mang lại bao nhiêu lợi ích về tốc độ, vì những lý do được thảo luận dưới đây.

Ý tưởng là thay thế CA không đồng bộ (từ đó là ACA) bằng CA đồng bộ ngẫu nhiên (SCA) hoạt động tương đương. Để làm điều này, trước tiên chúng ta tưởng tượng rằng ACA là một quá trình Poisson. Nghĩa là, thời gian tiến hành liên tục và mỗi ô là một xác suất không đổi trên mỗi đơn vị thời gian thực hiện chức năng cập nhật của nó, độc lập với các ô khác.

XTôijtTôijtTôij(0)~Exp(λ)λ là một tham số có giá trị có thể được chọn tùy ý.)

Ở mỗi bước thời gian logic, các ô của SCA được cập nhật như sau:

  • k,tôiTôi,jtktôi<tTôij

  • XTôijXktôiΔt~Exp(λ)tTôijtTôij+Δt

Tôi tin rằng điều này đảm bảo rằng các ô sẽ được cập nhật theo thứ tự có thể được "giải mã" để tương ứng với ACA ban đầu, đồng thời tránh va chạm và cho phép một số ô được cập nhật song song. Tuy nhiên, do điểm đầu tiên ở trên, điều đó có nghĩa là hầu hết các bộ xử lý GPU sẽ hầu như không hoạt động trên mỗi bước thời gian của SCA, ít hơn lý tưởng.

Tôi cần suy nghĩ thêm về việc liệu hiệu suất của thuật toán này có thể được cải thiện hay không và làm thế nào để mở rộng thuật toán này để đối phó với trường hợp nhiều ô được cập nhật đồng thời trong ACA. Tuy nhiên, nó có vẻ đầy hứa hẹn vì vậy tôi nghĩ rằng tôi sẽ mô tả nó ở đây trong trường hợp bất kỳ ai (a) biết bất cứ điều gì tương tự trong tài liệu, hoặc (b) có thể cung cấp bất kỳ cái nhìn sâu sắc nào về những vấn đề còn lại này.


Có lẽ bạn có thể hình thành vấn đề của mình theo cách tiếp cận dựa trên stprint. Nhiều phần mềm tồn tại cho các vấn đề dựa trên stprint. Bạn có thể xem tại: libgeodecomp.org/gallery.html , Trò chơi cuộc sống của Conway. Điều này có thể có một số điểm tương đồng.
vanCompute 17/2/13

@vanCompute trông giống như một công cụ tuyệt vời, nhưng từ cuộc điều tra ban đầu (khá khó hiểu) của tôi, có vẻ như mô hình mã stpson vốn đã đồng bộ, vì vậy có lẽ nó không phù hợp với những gì tôi đang cố gắng thực hiện. Tôi sẽ xem xét thêm, tuy nhiên.
Nathaniel

Bạn có thể cung cấp thêm một vài chi tiết về cách bạn sẽ song song hóa điều này bằng cách sử dụng SIMT không? Bạn sẽ sử dụng một chủ đề cho mỗi cặp? Hoặc công việc liên quan đến việc cập nhật một cặp có thể được trải rộng trên 32 chủ đề trở lên?
Pedro

@Pedro công việc liên quan đến việc cập nhật một cặp duy nhất khá nhỏ (về cơ bản chỉ là tổng hợp các vùng lân cận, cộng với một lần lặp của trình tạo số ngẫu nhiên và một exp()) vì vậy tôi sẽ không nghĩ rằng việc truyền bá nó qua nhiều luồng có ý nghĩa nhiều. Tôi nghĩ sẽ tốt hơn (và dễ dàng hơn cho tôi) để thử và cập nhật nhiều cặp song song, với một cặp cho mỗi luồng.
Nathaniel

Ok, và làm thế nào để bạn xác định một sự trùng lặp giữa các cặp cập nhật? Nếu các cặp tự chồng lên nhau, hoặc nếu vùng lân cận của chúng trùng nhau?
Pedro

Câu trả lời:


4

Tôi sẽ sử dụng tùy chọn đầu tiên và sẽ sử dụng chạy AC đồng bộ trước (sử dụng GPU), để phát hiện va chạm, thực hiện một bước của AC lục giác có quy tắc là giá trị của ô trung tâm = Sum (hàng xóm), CA này phải có bảy trạng thái nên được bắt đầu với ô được chọn ngẫu nhiên và trạng thái của chúng được xác minh trước khi chạy quy tắc cập nhật cho mỗi gpu.

Mẫu 1. Giá trị của một ô lân cận được chia sẻ

0 0 0 0 0 0 0

  0 0 1 0 0 0

0 0 0 0 0 0 0

  0 0 0 1 0 0

0 0 0 0 0 0 0

một bước của CA có quy tắc là ô trung tâm hình lục giác = Sum (hàng xóm)

0 0 1 1 0 0 0

  0 1 1 1 0 0

0 0 1 2 1 0 0

  0 0 1 1 1 0

0 0 0 1 1 0 0

Mẫu 2. Giá trị của một ô cần cập nhật được tính đến như một hàng xóm khác

0 0 0 0 0 0 0

  0 0 1 0 0 0

0 0 0 1 0 0 0

  0 0 0 0 0 0

0 0 0 0 0 0 0

Sau khi lặp lại

0 0 1 1 0 0 0

  0 1 2 2 0 0

0 0 2 2 1 0 0

  0 0 1 1 0 0

0 0 0 0 0 0 0

Mẫu 3. Không có mối quan hệ

  0 0 0 0 0 0

0 0 1 0 0 0 0

  0 0 0 0 0 0

0 0 0 0 0 0 0

  0 0 0 1 0 0

0 0 0 0 0 0 0

Sau khi lặp lại

  0 1 1 0 0 0

0 1 1 1 0 0 0

  0 1 1 0 0 0

0 0 0 1 1 0 0

  0 0 1 1 1 0

0 0 0 1 1 0 0


Ôi(n)n

Tôi nghĩ rằng có nhiều điều có thể được song song. Xử lý va chạm được thực hiện hoàn toàn trên GPU là một bước trong AC đồng bộ như được hiển thị trong liên kết được đăng ở trên. để xác minh sẽ sử dụng quy tắc cục bộ nếu Sum (hàng xóm) = 8 KHÔNG va chạm, Sum (hàng xóm)> 8 Va chạm, nó sẽ được xác minh trước khi chạy thay đổi quy tắc cập nhật của bạn nếu không có trạng thái ô va chạm, vì hai nên được đặt gần các điểm được đánh giá nếu chúng không gần nhau là thuộc về các ô khác.
jlopez1967

Tôi hiểu điều đó, nhưng vấn đề là, bạn sẽ làm gì khi phát hiện va chạm? Như tôi đã giải thích ở trên, thuật toán CA của bạn chỉ là bước đầu tiên để phát hiện xung đột. Bước thứ hai là tìm kiếm lưới cho các ô có trạng thái> = 2 và đây không phải là chuyện nhỏ.
Nathaniel

ví dụ: Hãy tưởng tượng rằng chúng tôi muốn phát hiện ô va chạm (5.7), trên automata di động và tổng thực hiện (hàng xóm của ô (5,7)) và nếu giá trị là 8 và nếu không có va chạm nào lớn hơn 8 thì không va chạm phải ở trong hàm đánh giá từng ô để xác định trạng thái tiếp theo của ô trong automata di động không đồng bộ. Việc phát hiện va chạm cho mỗi ô là một quy tắc cục bộ chỉ liên quan đến các ô lân cận của nó
jlopez1967

Có, nhưng câu hỏi chúng ta cần có thể trả lời để song song với một CA không đồng bộ không phải là "có một sự va chạm trong ô (5,7)" mà là "có một sự va chạm nào đó trên lưới không, và nếu vậy thì ở đâu hả Điều đó không thể được trả lời mà không lặp đi lặp lại trên lưới.
Nathaniel

1

Theo câu trả lời của bạn cho các câu hỏi của tôi trong các bình luận ở trên, tôi sẽ đề nghị bạn thử một cách tiếp cận dựa trên khóa trong đó mỗi luồng cố gắng khóa vùng lân cận mà nó sẽ cập nhật trước khi tính toán cập nhật thực tế.

Bạn có thể làm điều này bằng cách sử dụng các hoạt động nguyên tử được cung cấp trong CUDA và một mảng int chứa các ổ khóa cho mỗi tế bào, ví dụ lock. Mỗi luồng sau đó thực hiện như sau:

ci, cj = choose a pair at random.

int locked = 0;

/* Try to lock the cell ci. */
if ( atomicCAS( &lock[ci] , 0 , 1 ) == 0 ) {

    /* Try to lock the cell cj. */
    if ( atomicCAS( &lock[cj] , 0 , 1 ) == 0 ) {

        /* Now try to lock all the neigbourhood cells. */
        for ( cn = indices of all neighbours )
            if ( atomicCAS( &lock[cn] , 0 , 1 ) != 0 )
                break;

        /* If we hit a break above, we have to unroll all the locks. */
        if ( cn < number of neighbours ) {
            lock[ci] = 0;
            lock[cj] = 0;
            for ( int i = 0 ; i < cn ; i++ )
                lock[i] = 0;
            }

        /* Otherwise, we've successfully locked-down the neighbourhood. */
        else
            locked = 1;

        }

    /* Otherwise, back off. */
    else
        lock[ci] = 0;
    }

/* If we got everything locked-down... */
if ( locked ) {

    do whatever needs to be done...

    /* Release all the locks. */
    lock[ci] = 0;
    lock[cj] = 0;
    for ( int i = 0 ; i < cn ; i++ )
        lock[i] = 0;

    }

Lưu ý rằng phương pháp này có thể không phải là tối ưu nhất, nhưng nó có thể cung cấp một điểm khởi đầu thú vị. Nếu có rất nhiều va chạm giữa các luồng, tức là một hoặc nhiều hơn 32 luồng (như trong một va chạm trên mỗi sợi dọc), thì sẽ có một chút phân nhánh. Ngoài ra, các hoạt động nguyên tử có thể hơi chậm, nhưng vì bạn chỉ thực hiện các hoạt động so sánh và trao đổi, nên quy mô sẽ ổn.

Chi phí khóa có thể trông đáng sợ, nhưng thực sự chỉ có một vài nhiệm vụ và chi nhánh, không nhiều hơn thế.

Cũng lưu ý rằng tôi đang nhanh và lỏng lẻo với các ký hiệu trong các vòng lặp của ihàng xóm.

Phụ lục: Tôi đã đủ ung dung để cho rằng bạn có thể lùi lại khi cặp va chạm. Nếu đây không phải là trường hợp, thì bạn có thể bọc mọi thứ như dòng thứ hai trong một while-loop và thêm một breakở cuối của trận chung kếtif .

Tất cả các chủ đề sau đó sẽ phải đợi cho đến khi cuối cùng được thực hiện, nhưng nếu va chạm là hiếm, bạn sẽ có thể thoát khỏi nó.

Phụ lục 2: Không nên thêm các cuộc gọi vào __syncthreads()bất cứ nơi nào trong mã này, đặc biệt là phiên bản lặp được mô tả trong phụ lục trước! Asynchronicity là điều cần thiết để tránh va chạm lặp đi lặp lại trong trường hợp sau.


Cảm ơn, điều này có vẻ khá tốt. Có lẽ tốt hơn so với ý tưởng phức tạp mà tôi đang xem xét, và dễ thực hiện hơn nhiều. Tôi có thể tạo ra va chạm hiếm khi sử dụng lưới đủ lớn, điều này có thể tốt. Nếu phương pháp chỉ lùi lại nhanh hơn đáng kể, tôi có thể sử dụng nó để điều tra các thông số một cách không chính thức và chuyển sang phương thức chờ đợi cho mọi người để hoàn thành khi tôi cần tạo kết quả chính thức. Tôi sẽ cho nó thử một thời gian sớm.
Nathaniel

1

Tôi là nhà phát triển chính của LibGeoDecomp. Mặc dù tôi đồng ý với vanCompute rằng bạn có thể mô phỏng ACA của mình bằng CA, nhưng bạn đúng rằng điều này sẽ không hiệu quả lắm, vì chỉ có vài ô trong bất kỳ bước nào được đưa ra là được cập nhật. Đây thực sự là một ứng dụng rất thú vị - và thú vị để tinker!

Tôi khuyên bạn nên kết hợp các giải pháp được đề xuất bởi jlopez1967 và Pedro: Thuật toán của Pedro nắm bắt tốt sự song song, nhưng những khóa nguyên tử đó rất chậm. Giải pháp của jlopez1967 là thanh lịch khi phát hiện va chạm, nhưng kiểm tra tất cả các nô, khi chỉ có một tập hợp con nhỏ hơn (từ giờ tôi sẽ giả sử rằng có một số tham số kbiểu thị số lượng ô được cập nhật đồng thời) đang hoạt động, rõ ràng là cấm.

__global__ void markPoints(Cell *grid, int gridWidth, int *posX, int *posY)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x, y;
    generateRandomCoord(&x, &y);
    posX[id] = x;
    posY[id] = y;
    grid[y * gridWidth + x].flag = 1;
}

__global__ void checkPoints(Cell *grid, int gridWidth, int *posX, int *posY, bool *active)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x = posX[id];
    int y = posY[id];
    int markedNeighbors = 
        grid[(y - 1) * gridWidth + x + 0].flag +
        grid[(y - 1) * gridWidth + x + 1].flag +
        grid[(y + 0) * gridWidth + x - 1].flag +
        grid[(y + 0) * gridWidth + x + 1].flag +
        grid[(y + 1) * gridWidth + x + 0].flag +
        grid[(y + 1) * gridWidth + x + 1].flag;
    active[id] = (markedNeighbors > 0);
}


__global__ void update(Cell *grid, int gridWidth, int *posX, int *posY, bool *active)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    int x = posX[id];
    int y = posY[id];
    grid[y * gridWidth + x].flag = 0;
    if (active[id]) {
        // do your fancy stuff here
    }
}

int main() 
{
  // alloc grid here, update up to k cells simultaneously
  int n = 1024 * 1024;
  int k = 1234;
  for (;;) {
      markPoints<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY);
      checkPoints<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY, active);
      update<<<gridDim,blockDim>>>(grid, gridWidth, posX, posY, active);
  }
}

Trong trường hợp không đồng bộ hóa toàn cầu tốt trên GPU, bạn cần phải gọi nhiều nhân cho các giai đoạn khác nhau. Trên Kepler của Nvidia, bạn có thể di chuyển ngay cả vòng lặp chính sang GPU, nhưng tôi không hy vọng điều đó sẽ thu được nhiều.

Các thuật toán đạt được mức độ song song (cấu hình). Tôi đoán, câu hỏi thú vị là liệu các va chạm sẽ ảnh hưởng đến phân phối ngẫu nhiên của bạn khi bạn tăng lên k.


0

Tôi đề nghị với bạn rằng bạn nên xem liên kết này http://www.wolfram.com/training/cifts/hpc021.html khoảng 14:15 phút trong khóa học, đào tạo toán học trong đó họ thực hiện một chương trình tự động di động bằng CUDA , từ đó và bạn có thể sửa đổi nó.


Thật không may, đó là một CA đồng bộ, là một loại quái thú khá khác với các loại không đồng bộ mà tôi đang xử lý. Trong một CA đồng bộ, mọi ô được cập nhật đồng thời và điều này dễ song song trên GPU, nhưng trong CA không đồng bộ, một ô được chọn ngẫu nhiên duy nhất được cập nhật mỗi bước (thực tế trong trường hợp của tôi là hai ô lân cận) và điều này tạo ra song song khó hơn rất nhiều. Các vấn đề được nêu trong câu hỏi của tôi là cụ thể để cần một chức năng cập nhật không đồng bộ.
Nathaniel
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.