Mới: Chúng tôi đã bắt đầu sử dụng Rust trong Qiskit để có hiệu suất tốt hơn

Apr 09 2022
Bởi Matthew Treinish, Kỹ sư phần mềm cao cấp tại IBM Quantum Gần đây, chúng tôi đã bắt đầu sử dụng ngôn ngữ lập trình Rust ngoài Python trong Qiskit, điều này đã dẫn đến những cải tiến hiệu suất đáng kể. Mặc dù Python vẫn đang được sử dụng làm ngôn ngữ lập trình chính cho phần lớn Qiskit, nhưng một số phần quan trọng về hiệu suất của Qiskit hiện đã được triển khai trong Rust.

Bởi Matthew Treinish , Kỹ sư phần mềm cao cấp tại IBM Quantum

Gần đây, chúng tôi đã bắt đầu sử dụng ngôn ngữ lập trình Rust ngoài Python trong Qiskit, điều này đã dẫn đến những cải tiến hiệu suất đáng kể. Mặc dù Python vẫn đang được sử dụng làm ngôn ngữ lập trình chính cho phần lớn Qiskit, nhưng một số phần quan trọng về hiệu suất của Qiskit hiện đã được triển khai trong Rust. Bài đăng trên blog này sẽ giải thích chi tiết về thay đổi gần đây này và ý nghĩa của nó đối với toàn bộ dự án Qiskit:

• Tại sao Gỉ?

• Tác động đến người dùng

• Lợi ích về Hiệu suất

StochasticSwap

DenseLayout

○ Tính toán giá trị kỳ vọng thông tin lượng tử

•Làm thế nào nó hoạt động

•Nhìn về phía trước

Tại sao gỉ?

Đôi khi chúng tôi cần sử dụng ngôn ngữ đã biên dịch để có được hiệu suất thời gian chạy nhanh hơn cho các phần quan trọng về hiệu suất của Qiskit. Thông thường, chúng ta có thể làm điều này bằng cách sử dụng thư viện Python bên ngoài giúp quản lý việc tách các ngôn ngữ lập trình dễ dàng hơn. Ví dụ, chúng tôi sử dụng rất nhiều numpy và scipy để tăng tốc đại số tuyến tính trong Qiskit. Ngoài ra, retworkx được tạo ra cho Qiskit để tăng tốc các thuật toán đồ thị - Bạn có thể tham khảo bài đăng trên blog retworkx trước đó của tôiđể có một cái nhìn tổng quan. Tuy nhiên, không phải mọi thuật toán được sử dụng trong Qiskit đều phù hợp với một thư viện độc lập và một số thuật toán được tích hợp chặt chẽ với mã Qiskit. Trong những trường hợp này, chúng ta cần viết một số quy trình bằng ngôn ngữ đã biên dịch và gói là một phần của Qiskit trực tiếp (hay còn gọi là mô-đun mở rộng Python hoặc một phần mở rộng đã biên dịch).

Trước đây, chúng tôi đã làm điều này trong Qiskit bằng cách sử dụng Cython. Cython cho phép bạn viết mã theo cú pháp giống Python và mã Cython đó sau đó sẽ tạo ra các tệp nguồn C hoặc C ++, sau đó có thể được biên dịch như một phần của quy trình xây dựng gói Python thông thường. Cython là một công cụ tuyệt vời cho phép bạn dễ dàng trộn và kết hợp mã Python và C / C ++. Tuy nhiên, chúng tôi bắt đầu gặp phải những hạn chế trong các tình huống mà chúng tôi cần kiểm soát nhiều hơn đối với mã đã biên dịch. Ví dụ: khi chúng tôi muốn tận dụng đa luồng, chúng tôi đã gặp phải một số vấn đề xung quanh việc triển khai và tính di động / hỗ trợ nền tảng. Mặc dù điều này về mặt lý thuyết có thể quản lý được trong Cython, nhưng do khó khăn xung quanh việc duy trì nó, chúng tôi quyết định sẽ dễ dàng hơn và đơn giản hơn nếu quản lý trực tiếp điều này bằng một ngôn ngữ đã biên dịch và xây dựng giao diện cho Python trên đó.

Đây là lúc việc sử dụng Rust trở nên hữu ích. Đối với những người không quen thuộc với Rust, cuốn sách trực tuyến “Ngôn ngữ lập trình Rust” có một bản tóm tắt hay:

Rust là một ngôn ngữ lập trình tập trung vào sự an toàn, tốc độ và tính đồng thời. Thiết kế của nó cho phép bạn tạo các chương trình có hiệu suất và khả năng kiểm soát của ngôn ngữ cấp thấp, nhưng với sự trừu tượng hóa mạnh mẽ của ngôn ngữ cấp cao. Các thuộc tính này làm cho Rust phù hợp với các lập trình viên có kinh nghiệm về các ngôn ngữ như C và đang tìm kiếm một giải pháp thay thế an toàn hơn, cũng như những người từ các ngôn ngữ như Python, những người đang tìm cách viết mã hoạt động tốt hơn mà không phải hy sinh khả năng diễn đạt.

Bằng cách sử dụng Rust, chúng ta có thể viết mã nhanh và tận dụng tất cả những lợi thế mà ngôn ngữ lập trình Rust mang lại. Mặc dù có sẵn các khuôn khổ để thực hiện một việc tương tự bằng các ngôn ngữ khác (ví dụ: pybind11 cho C ++, đó là cách Qiskit Aer được xây dựng), việc sử dụng Rust mang lại hai lợi thế chính: thứ nhất, hệ thống đóng gói và xây dựng tích hợp và thứ hai, bộ nhớ an toàn được tích hợp trong ngôn ngữ.

Rust đóng gói và xây dựng hệ thống trung tâm xung quanh cargo, bao bọc trình biên dịch Rust và quản lý phụ thuộc thành một công cụ duy nhất. Để tích hợp với Python, điều này làm cho mọi thứ trở nên cực kỳ dễ dàng vì chúng ta không phải lo lắng về sự khác biệt môi trường cục bộ và các trình biên dịch khác nhau, quản lý phụ thuộc hoặc bất kỳ sự phức tạp nào khác thường đi kèm với việc xây dựng một thư viện đã biên dịch. Mã nguồn cho thư viện Rust nội bộ mới hiện là một phần của Qiskit nằm trong một thư mục độc lập src/ khi chúng ta xây dựng và cài đặt thư viện Rust như một phần của quá trình cài đặt Python thông thường. Trong quá trình xây dựng gói, Python gọicargođể xây dựng mã Rust của chúng tôi và mọi thứ được xử lý cho chúng tôi. Thư viện Rust được biên dịch thành một tệp thư viện động (với tất cả các phụ thuộc Rust được liên kết tĩnh) và được cài đặt dưới dạng qiskit._acceleratemô-đun trong cây gói Python. Sau đó, chúng tôi có quyền truy cập vào API Python mà chúng tôi hiển thị từ Rust trực tiếp từ mô-đun đó.

Khía cạnh thứ hai, an toàn bộ nhớ, là một trong những ưu điểm chính của ngôn ngữ lập trình Rust. Trình biên dịch Rust thực hiện kiểm tra thời gian biên dịch để đảm bảo rằng mã không chứa các lỗi an toàn bộ nhớ như con trỏ null, con trỏ treo hoặc cuộc đua dữ liệu mà không ảnh hưởng đến hiệu suất thời gian chạy, vì việc kiểm tra diễn ra tại thời điểm biên dịch. Mặc dù đây thường là hành vi tốt, nhưng điều này đặc biệt hữu ích trong trường hợp của chúng tôi, bởi vì một phần lý do chúng tôi đang sử dụng Rust bây giờ là để viết các hàm đa luồng. Một trong những người thuê thiết kế của ngôn ngữ lập trình Rust là tính đồng thời không sợ hãi, ý tưởng rằng bạn có thể viết mã song song mà không phải lo lắng về sự an toàn của luồng vì trình biên dịch sẽ kiểm tra điều đó cho bạn và báo lỗi nếu bạn làm điều gì đó không chắc chắn. Đối với trường hợp sử dụng của Qiskit, Rust rất phù hợp vì nó có nghĩa là chúng ta có thể viết các hàm đa luồng và không phải lo lắng về toàn bộ lớp lỗi trong thời gian chạy.

Tác động đến người dùng

Phần lớn, việc chuyển sang sử dụng Rust trong Qiskit thay vì Cython sẽ không ảnh hưởng đến đại đa số người dùng. Nếu bạn chỉ đang cài đặt phiên bản Qiskit đã phát hành, bạn sẽ không nhận thấy nhiều; các gói chúng tôi xuất bản cho Qiskit trên mọi bản phát hành đều được biên dịch trước và sẽ không có gì thay đổi. Bạn có thể tiếp tục sử dụng pip để cài đặt phiên bản mới nhất của Qiskit và mọi thứ sẽ hoạt động như trước đây.

Tuy nhiên, nếu bạn đang xây dựng Qiskit từ nguồn vì bất kỳ lý do gì, sẽ có những yêu cầu mới. Đối với hầu hết các phần, bạn sẽ chỉ cần cài đặt một trình biên dịch Rust. Trình cài đặt gỉ làm cho việc này trở nên đơn giản đối với hầu hết các trường hợp sử dụng. Sau khi bạn đã cài đặt trình biên dịch gỉ, quy trình làm việc dựa trên pip thông thường sẽ giống nhau. Mặc dù đây là một yêu cầu mới, nhưng nó cung cấp một số tùy chọn bổ sung nếu bạn đang xây dựng các gói từ nguồn. Ví dụ: nếu bạn muốn điều chỉnh gói của mình để có hiệu suất tối đa trên phần cứng cục bộ, bạn có thể chạy:

RUSTFLAGS="-Ctarget-cpu=native pip install --no-binary qiskit-terra qiskit-terra

Lợi ích về hiệu suất

StochasticSwap

Đoạn StochasticSwapmã chuyển tiếp là lý do chính mà chúng tôi bắt đầu xem xét Rust. StochasticSwaplà đường truyền định tuyến mặc định cho trình biên dịch của Qiskit ở các mức tối ưu hóa 0, 1 và 2, có nghĩa là nó là thứ được transpile()hàm sử dụng hầu hết thời gian. Vì vậy, hiệu suất của đường chuyền này là rất quan trọng, đặc biệt là vì khi kích thước của mạch lượng tử đang được biên dịch và kích thước của máy tính lượng tử mục tiêu tăng lên, thuật toán định tuyến ngày càng chạy chậm hơn. Khi xem xét các cách để cải thiện điều này, cách đơn giản nhất là song song hóa thuật toán. Về cốt lõi, thuật toán chạy một số thử nghiệm ngẫu nhiên để cố gắng tìm ánh xạ hoán đổi tốt nhất cho mỗi lớp trong mạch. Trước đó, các thử nghiệm này đã được chạy nối tiếp thông qua một mô-đun Cython (đã được thêm vào Qiskit / qiskit-terra # 1789). Tuy nhiên, chúng tôi có thể chạy các thử nghiệm song song và thuật toán sẽ hoạt động giống như trước đây, nhưng chỉ thực thi nhanh hơn. Điều này trước đây đã được thử theo một số cách khác nhau nhưng sử dụng Rust để hoàn thành nhiệm vụ là phù hợp nhất và mang lại hiệu suất tốt nhất. Quá trình di chuyển StochasticSwapthẻ đã được thực hiện trong Qiskit / qiskit-terra # 7658 .

Để đánh giá cách triển khai Rust cải thiện hiệu suất, tôi đã chạy tập lệnh sau với cả phiên bản Rust song song mới và phiên bản Cython nối tiếp trước đó:

Sau đó, tôi vẽ biểu đồ tỷ lệ hiệu suất thời gian chạy giữa hai lần chạy:

Trong biểu đồ trên, chúng tôi đang hiển thị tỷ lệ thời gian chạy Rust so với thời gian chạy Cython để chạy StochasticSwaptrên mạch Khối lượng tử có kích thước nhất định nhắm mục tiêu đến một thiết bị có bản đồ ghép hình lục giác nặng và số lượng qubit khác nhau. Giá trị 1 ở đó (được hiển thị là màu trắng) có nghĩa là không có sự khác biệt về hiệu suất. Các giá trị dưới 1 (được biểu thị bằng các phần xanh hơn trên cốt truyện) cho thấy phiên bản Rust mới hoạt động tốt hơn trong khi các giá trị trên 1 (được biểu thị bằng màu đỏ nhiều hơn trên cốt truyện) cho thấy phiên bản Cython cũ nhanh hơn.

Hiệu suất tốt nhất cho việc triển khai Rust nhanh hơn ~ 7,5 lần so với Cython nối tiếp (đối với các mạch lớn hơn trên các thiết bị lớn hơn).

DenseLayout

Trong các cấp độ tối ưu hóa 1 và 2, DenseLayoutchuyển đoạn truyền là phương pháp mặc định được sử dụng để tìm một bố cục nội bộ (ánh xạ các qubit ảo trong mạch sang các qubit vật lý trên phụ trợ). Khi xem xét hiệu suất thời gian chạy của các mạch biên dịch> 1000 qubit sau khi thực hiện lại StochasticSwaptrong Rust, thời gian chạy của StochasticSwapDenseLayoutgần như tương đương. Điều này có vấn đề vì thuật toán trong DenseLayoutkhá đơn giản và nó sẽ không chậm như vậy StochasticSwap. Trước đây, chúng tôi không thực sự nhận thấy tỷ lệ kém trong DenseLayoutđường chuyền vì so với việc triển khai trước StochasticSwapđó vẫn còn nhanh. CácDenseLayoutpass ban đầu được viết bằng Python sử dụng ma trận thưa thớt scipy và việc viết lại cốt lõi của thuật toán thành đa luồng trong Rust tương đối đơn giản. Điều này đã được thực hiện trong Qiskit / qiskit-terra # 7740

Sau khi viết lại đường chuyền bằng Rust, chúng tôi thấy tốc độ tăng lên đến 3 bậc trong trường hợp tốt nhất:

Trong biểu đồ này, trục Y là hệ số tăng tốc của quá trình triển khai Rust, vì vậy nếu con số là 100,0 thì phiên bản Rust mới nhanh hơn 100 lần so với lần triển khai trước đó. Đường màu đỏ được đặt theo tỷ lệ 1. Nếu đường màu xanh lam ở dưới màu đỏ thì phiên bản cũ nhanh hơn, và nếu ở trên đường màu đỏ thì phiên bản Rust mới nhanh hơn.

Biểu đồ này được tạo bằng cách chạy tập lệnh sau với cả phiên bản Rust mới và phiên bản gốc, sau đó vẽ biểu đồ tỷ lệ Old version time / Rust version timecho mỗi điểm dữ liệu.

Tính toán giá trị kỳ vọng thông tin lượng tử

Vị trí thứ hai mà chúng ta đang tận dụng Rust ngay bây giờ là một phần nội bộ của phương thức Statevector.expectation_value()DensityMatrix.expectation_value(). Điều này ban đầu được viết bằng Cython để tăng tốc các chức năng này. Mặc dù đây không phải là một thói quen quan trọng đối với hoạt động của Qiskit StochasticSwap, nhưng chúng tôi quyết định viết lại nó trong Rust vì đây là cách sử dụng Cython duy nhất còn lại sau khi di chuyển. Điều này đã được thực hiện trong Qiskit / qiskit-terra # 7702 . Sau khi quá trình di chuyển hoàn tất, chúng tôi đã thực hiện một số điểm chuẩn nhanh để hiển thị các đặc điểm mở rộng chung của triển khai Rust mới so với triển khai Cython trước đó:

Trong biểu đồ này, trục Y là hệ số tăng tốc của Rust, vì vậy nếu con số là 5.0 thì phiên bản Rust mới nhanh hơn phiên bản Cython cũ gấp 5 lần. Đường màu đỏ có tỷ lệ là 1,0 nghĩa là Rust có cùng tốc độ với Cython. Nếu đường màu xanh nằm dưới màu đỏ thì phiên bản Cython cũ nhanh hơn và phía trên đường màu đỏ là phiên bản Rust mới nhanh hơn.

Biểu đồ này được tạo bằng cách chạy tập lệnh sau với cả phiên bản Rust và phiên bản Cython, sau đó vẽ biểu đồ tỷ lệ Cython time / Rust timecho mỗi điểm dữ liệu.

Cũng cần lưu ý rằng đa luồng cho chức năng này chỉ bắt đầu ở 19 qubit. Dưới 19 qubit, việc triển khai Rust là một luồng đơn giống như phiên bản Cython.

Lưu ý: Tất cả các điểm chuẩn đều được chạy trên Bộ xử lý AMD Ryzen Threadripper 3970X 32-Core với Python 3.10 trên Linux. Hiệu suất có thể sẽ khác trên hệ thống cục bộ của bạn, đặc biệt là khi nhiều luồng đang được sử dụng.

Làm thế nào nó hoạt động

Mặc dù việc sử dụng Rust trong Qiskit hiện tại là tương đối ít, nhưng cách chúng tôi đã xây dựng tích hợp đủ dễ dàng để mở rộng theo thời gian. Đối với các hàm mà chúng ta cần tăng tốc với Rust, chúng ta tạo các hàm và cấu trúc dữ liệu Rust độc lập để triển khai các chức năng cần thiết. Sau đó, chúng tôi tận dụng thư viện PyO3 để xây dựng giao diện của chúng tôi sang Python. PyO3 làm cho việc xây dựng giao diện Python sang mã Rust khá đơn giản. Nó cung cấp các macro Rust cho phép bạn viết mã vanilla Rust, mã này sẽ tự động tạo giao diện hàm ngoại C (FFI), cung cấp API Python C cho phép trình thông dịch Python gọi hàm Rust của bạn. Ví dụ: nếu bạn muốn viết một hàm Rust sẽ nhận một số nguyên từ Python và trả về gấp 2 lần số nguyên đó, bạn có thể làm như sau:

Macro ở đó sẽ tự động #[pyfunction]tạo C FFI cho Python để sử dụng tại thời điểm biên dịch. C FFI được tạo ra này xử lý việc chuyển đổi giữa các kiểu Python và Rust, và tất cả mã soạn sẵn để tương tác với Python. Điều này cho phép chúng tôi tóm tắt hầu hết các chi tiết để tương tác với Python và tập trung vào việc viết mã Rust cho chức năng chúng tôi cần. Cũng có các macro tương tự để tạo các lớp và mô-đun Python.

Mô hình này cho phép chúng tôi giữ ranh giới giữa mã Rust và mã Python khá rõ ràng và cũng cho phép chúng tôi linh hoạt để tận dụng thế mạnh của cả hai ngôn ngữ một cách khá dễ dàng. Khi chúng ta cần tận dụng chức năng Rust, chúng ta có thể triển khai nó nguyên bản trong Rust mà không cần phải xử lý thủ công với việc duy trì sự tách biệt giữa Rust và Python (trừ khi chúng ta cần).

Để tích hợp hệ thống đóng gói và xây dựng với Python, chúng tôi tận dụng setuptools-rustthư viện để tích hợp gọi cargoxây dựng mã Rust và quản lý cài đặt tệp thư viện động nhị phân đã biên dịch vào đúng vị trí trong cây gói Qiskit để Python có thể tải nó. Điều này có nghĩa là hầu hết mọi người thậm chí không cần phải nghĩ về mã Rust. Miễn là họ đã cài đặt trình biên dịch Rust, thì việc đóng gói Python bình thường sẽ tự động xử lý mọi thứ. Điều này là lý tưởng vì Qiskit chủ yếu vẫn là một thư viện Python và hầu hết các nhà phát triển (và người dùng) không muốn phải đối phó với sự phức tạp thêm khi có thêm một ngôn ngữ lập trình.

Nhìn về phía trước

Mặc dù hiện tại, việc sử dụng Rust trực tiếp bên trong Qiskit của chúng tôi là khá ít, nhưng nhìn về tương lai, chúng tôi có thể bắt đầu sử dụng Rust ở nhiều nơi hơn. Với Rust hiện được tích hợp vào quá trình phát triển Qiskit, chúng tôi có thể sẽ bắt đầu xem xét việc sử dụng nó để tăng tốc nhiều chức năng hơn trong Qiskit. Cách chúng tôi đã tích hợp Rust vào Qiskit giúp việc mở rộng sử dụng nó trở nên đơn giản nếu cần. Vì vậy, ở những nơi mà chúng tôi gặp vấn đề về hiệu suất hoặc vấn đề về quy mô, chúng tôi có thể bắt đầu tận dụng lợi thế của việc sử dụng Rust để thử và giải quyết những nơi đó.

Bạn có phải là nhà phát triển Rust không? Chúng tôi hoan nghênh những đóng góp từ cộng đồng cho qiskit-terra và retworkx

© Copyright 2021 - 2023 | vngogo.com | All Rights Reserved