From 50e07e1578df36fe4475d66dcfa37dbb4591a349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Tue, 28 Mar 2023 22:16:59 +0200 Subject: [PATCH 01/22] Fix numpy version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index faabd03..189d17f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "dimod ~= 0.12", "numba ~= 0.56.4", "pluggy ~= 0.13.1", - "numpy ~= 1.19.4" + "numpy ~= 1.20.3" ] dynamic = ["version"] @@ -22,7 +22,7 @@ docs = [ "sphinx-autoapi~=2.0.1", "pydata-sphinx-theme~=0.13.1", "sphinx-term~=0.1", - "myst-parser~=1.0.0" + "myst-parser~=1.0.0", ] [project.entry-points."omnisolver"] From 637e0cd788e747604a2060052607bab75828afc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Tue, 28 Mar 2023 22:17:46 +0200 Subject: [PATCH 02/22] Add printing ofr estimates for grid and block size --- omnisolver/extensions/bruteforce_gpu.cu | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index 03d62d1..f5bb4eb 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -192,6 +192,17 @@ void search( // Analogously, device vectors for states. state_vector states(chunk_size), best_states(num_states + chunk_size); + + // here + int blockSize; + int gridSize; + + cudaOccupancyMaxPotentialBlockSize( &gridSize, &blockSize, init, 0, 0); + std::cout << "Grid for init " << blockSize << " " << gridSize << " " << std::endl; + cudaOccupancyMaxPotentialBlockSize( &gridSize, &blockSize, single_step, 0, 0); + std::cout << "Grid for single step " << blockSize << " " << gridSize << " " << std::endl; + // here ends + // For easier iteration: tuple iterators over current and best spectrum auto current_spectrum_it = zip(states, energies); auto best_spectrum_it = zip(best_states, best_energies); From c07a4592b9887d21d6c77178c9cb10b33cac75f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Tue, 28 Mar 2023 22:18:29 +0200 Subject: [PATCH 03/22] Add draft of distributed runner --- omnisolver/bruteforce/gpu/distributed.py | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 omnisolver/bruteforce/gpu/distributed.py diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py new file mode 100644 index 0000000..10b2e6d --- /dev/null +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -0,0 +1,87 @@ +from itertools import product +import typing + +from dimod import append_variables, concatenate, Sampler, Vartype +import numpy as np +import ray + +from .sampler import BruteforceGPUSampler + + +@ray.remote(num_gpus=1) +def _solve_subproblem( + bqm, + num_states, + fixed_vars, + suffix_size, + grid_size, + block_size, + dtype +): + new_bqm = bqm.copy() + new_bqm.fix_variables(fixed_vars) + + sampler = BruteforceGPUSampler() + result = sampler.sample( + new_bqm, + num_states, + suffix_size, + grid_size, + block_size, + dtype + ) + + return append_variables(result, fixed_vars) + + +class DistributedBruteforceGPUSampler(Sampler): + + def sample( + self, bqm, num_states, num_fixed_vars, suffix_size, grid_size, block_size, dtype=np.float32 + ): + if bqm.vartype == Vartype.SPIN: + return self.sample( + bqm.change_vartype("BINARY", inplace=False), + num_states, + suffix_size, + grid_size, + block_size, + ).change_vartype("SPIN", inplace=False) + + bqm, mapping = bqm.relabel_variables_as_integers() + + subproblems = [ + {i: v for i, v in enumerate(vals)} + for vals in product([0, 1], repeat=num_fixed_vars) + ] + + refs = [ + _solve_subproblem.remote( + bqm, + num_states, + fixed_vars, + suffix_size, + grid_size, + block_size, + dtype + ) + for fixed_vars in subproblems + ] + + subsolutions = [ray.get(ref) for ref in refs] + return concatenate(subsolutions).truncate(num_states) + + + @property + def parameters(self) -> typing.Dict[str, typing.Any]: + return { + "num_states": [], + "suffix_size": [], + "grid_size": [], + "block_size": [], + "dtype": [], + } + + @property + def properties(self) -> typing.Dict[str, typing.Any]: + return {} From 18abfe9e885971f74049b53d75616c22b3a57b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Tue, 28 Mar 2023 22:19:10 +0200 Subject: [PATCH 04/22] Add example script using distributed runner --- examples/distributed.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/distributed.py diff --git a/examples/distributed.py b/examples/distributed.py new file mode 100644 index 0000000..34396c0 --- /dev/null +++ b/examples/distributed.py @@ -0,0 +1,63 @@ +from dimod import BQM, Vartype +from omnisolver.bruteforce.gpu.distributed import DistributedBruteforceGPUSampler +from omnisolver.bruteforce.gpu import BruteforceGPUSampler +from numpy.random import default_rng +import numpy as np + + +def random_bqm(num_variables, vartype, offset, rng): + linear = { + i: coef for i, coef in zip(range(num_variables), rng.uniform(-2, 2, size=num_variables)) + } + quadratic = { + (i, j): coef + for (i, j), coef in zip( + [(i, j) for i in range(num_variables) for j in range(i + 1, num_variables)], + rng.uniform(-1, -1, size=(num_variables - 1) * num_variables // 2), + ) + } + return BQM(linear, quadratic, offset, vartype=vartype) + +NUM_VARIABLES = 40 + + +def main(): + sampler = DistributedBruteforceGPUSampler() + sampler2 = BruteforceGPUSampler() + rng = default_rng(1234) + + bqm = random_bqm(NUM_VARIABLES, "BINARY", 0.0, rng) + + import time + + start = time.perf_counter() + result = sampler.sample( + bqm, + num_states=100, + num_fixed_vars=1, + suffix_size=25, + grid_size=1024, + block_size=1024 + ) + duration = time.perf_counter() - start + distributed_en = [entry.energy for entry in result.data()] + + print(f"Distributed finished in : {duration}s") + + start = time.perf_counter() + result2 = sampler2.sample( + bqm, + num_states=100, + suffix_size=25, + grid_size=2 ** 12, + block_size=512 + ) + duration = time.perf_counter() - start + single_en = [entry.energy for entry in result2.data()] + print(f"Single finished in: {duration}s") + + print(max(abs(en1 - en2) for en1, en2 in zip(distributed_en, single_en))) + + +if __name__ == "__main__": + main() From b46d397ddbb622807eb7ac4d6ab134b57aae447e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Thu, 20 Jul 2023 16:02:08 +0200 Subject: [PATCH 05/22] Implement measuring time + style fixes --- examples/distributed.py | 22 ++++-------- omnisolver/bruteforce/gpu/distributed.py | 46 ++++++++---------------- omnisolver/extensions/bruteforce_gpu.cu | 11 ------ tests/cpu/test_gray_code.py | 12 +++---- 4 files changed, 26 insertions(+), 65 deletions(-) diff --git a/examples/distributed.py b/examples/distributed.py index 34396c0..aab4250 100644 --- a/examples/distributed.py +++ b/examples/distributed.py @@ -1,8 +1,8 @@ -from dimod import BQM, Vartype -from omnisolver.bruteforce.gpu.distributed import DistributedBruteforceGPUSampler -from omnisolver.bruteforce.gpu import BruteforceGPUSampler +from dimod import BQM from numpy.random import default_rng -import numpy as np + +from omnisolver.bruteforce.gpu import BruteforceGPUSampler +from omnisolver.bruteforce.gpu.distributed import DistributedBruteforceGPUSampler def random_bqm(num_variables, vartype, offset, rng): @@ -18,6 +18,7 @@ def random_bqm(num_variables, vartype, offset, rng): } return BQM(linear, quadratic, offset, vartype=vartype) + NUM_VARIABLES = 40 @@ -32,12 +33,7 @@ def main(): start = time.perf_counter() result = sampler.sample( - bqm, - num_states=100, - num_fixed_vars=1, - suffix_size=25, - grid_size=1024, - block_size=1024 + bqm, num_states=100, num_fixed_vars=1, suffix_size=25, grid_size=1024, block_size=1024 ) duration = time.perf_counter() - start distributed_en = [entry.energy for entry in result.data()] @@ -46,11 +42,7 @@ def main(): start = time.perf_counter() result2 = sampler2.sample( - bqm, - num_states=100, - suffix_size=25, - grid_size=2 ** 12, - block_size=512 + bqm, num_states=100, suffix_size=25, grid_size=2**12, block_size=512 ) duration = time.perf_counter() - start single_en = [entry.energy for entry in result2.data()] diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py index 10b2e6d..1ea206b 100644 --- a/omnisolver/bruteforce/gpu/distributed.py +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -1,41 +1,26 @@ -from itertools import product import typing +from itertools import product +from time import perf_counter -from dimod import append_variables, concatenate, Sampler, Vartype import numpy as np import ray +from dimod import Sampler, Vartype, append_variables, concatenate from .sampler import BruteforceGPUSampler @ray.remote(num_gpus=1) -def _solve_subproblem( - bqm, - num_states, - fixed_vars, - suffix_size, - grid_size, - block_size, - dtype -): +def _solve_subproblem(bqm, num_states, fixed_vars, suffix_size, grid_size, block_size, dtype): new_bqm = bqm.copy() new_bqm.fix_variables(fixed_vars) sampler = BruteforceGPUSampler() - result = sampler.sample( - new_bqm, - num_states, - suffix_size, - grid_size, - block_size, - dtype - ) + result = sampler.sample(new_bqm, num_states, suffix_size, grid_size, block_size, dtype) return append_variables(result, fixed_vars) class DistributedBruteforceGPUSampler(Sampler): - def sample( self, bqm, num_states, num_fixed_vars, suffix_size, grid_size, block_size, dtype=np.float32 ): @@ -43,6 +28,7 @@ def sample( return self.sample( bqm.change_vartype("BINARY", inplace=False), num_states, + num_fixed_vars, suffix_size, grid_size, block_size, @@ -50,27 +36,25 @@ def sample( bqm, mapping = bqm.relabel_variables_as_integers() + start_counter = perf_counter() + subproblems = [ - {i: v for i, v in enumerate(vals)} - for vals in product([0, 1], repeat=num_fixed_vars) + {i: v for i, v in enumerate(vals)} for vals in product([0, 1], repeat=num_fixed_vars) ] refs = [ _solve_subproblem.remote( - bqm, - num_states, - fixed_vars, - suffix_size, - grid_size, - block_size, - dtype + bqm, num_states, fixed_vars, suffix_size, grid_size, block_size, dtype ) for fixed_vars in subproblems ] - subsolutions = [ray.get(ref) for ref in refs] - return concatenate(subsolutions).truncate(num_states) + solve_time_in_seconds = perf_counter() - start_counter + subsolutions = [ray.get(ref) for ref in refs] + result = concatenate(subsolutions).truncate(num_states) + result.info["solve_time_in_seconds"] = solve_time_in_seconds + return result @property def parameters(self) -> typing.Dict[str, typing.Any]: diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index f5bb4eb..03d62d1 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -192,17 +192,6 @@ void search( // Analogously, device vectors for states. state_vector states(chunk_size), best_states(num_states + chunk_size); - - // here - int blockSize; - int gridSize; - - cudaOccupancyMaxPotentialBlockSize( &gridSize, &blockSize, init, 0, 0); - std::cout << "Grid for init " << blockSize << " " << gridSize << " " << std::endl; - cudaOccupancyMaxPotentialBlockSize( &gridSize, &blockSize, single_step, 0, 0); - std::cout << "Grid for single step " << blockSize << " " << gridSize << " " << std::endl; - // here ends - // For easier iteration: tuple iterators over current and best spectrum auto current_spectrum_it = zip(states, energies); auto best_spectrum_it = zip(best_states, best_energies); diff --git a/tests/cpu/test_gray_code.py b/tests/cpu/test_gray_code.py index 0a7964f..aaff5be 100644 --- a/tests/cpu/test_gray_code.py +++ b/tests/cpu/test_gray_code.py @@ -33,14 +33,10 @@ def test_gray_code_index_is_computed_correctly(self, binary_number, gray_code): @pytest.mark.parametrize("num_bits", [10, 12, 16]) class TestBijectionBetweenGrayAndBinary: def test_nth_gray_code_inverts_gray_code_index(self, num_bits): - assert all( - gray_code_index(nth_gray_number(n)) == n for n in range(2 ** num_bits) - ) + assert all(gray_code_index(nth_gray_number(n)) == n for n in range(2**num_bits)) def test_gray_code_index_inverts_nth_gray_code(self, num_bits): - assert all( - nth_gray_number(gray_code_index(n)) == n for n in range(2 ** num_bits) - ) + assert all(nth_gray_number(gray_code_index(n)) == n for n in range(2**num_bits)) def _binary_array_to_number(arr): @@ -57,12 +53,12 @@ def test_iterating_algorithm_l_yields_all_gray_codes(num_bits): state = np.zeros(num_bits, dtype=np.int8) produced_numbers = [_binary_array_to_number(state)] - for _ in range(2 ** num_bits - 1): + for _ in range(2**num_bits - 1): bit_to_flip = focus_vector[0] % num_bits state[bit_to_flip] = 1 - state[bit_to_flip] advance_focus_vector(focus_vector) produced_numbers.append(_binary_array_to_number(state)) np.testing.assert_array_equal( - produced_numbers, [nth_gray_number(n) for n in range(2 ** num_bits)] + produced_numbers, [nth_gray_number(n) for n in range(2**num_bits)] ) From f7dc74203c69afddfcf038827ce1300cbe0de87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Thu, 20 Jul 2023 17:49:04 +0200 Subject: [PATCH 06/22] Fix measuring time --- omnisolver/bruteforce/gpu/distributed.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py index 1ea206b..1f0580d 100644 --- a/omnisolver/bruteforce/gpu/distributed.py +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -49,9 +49,8 @@ def sample( for fixed_vars in subproblems ] - solve_time_in_seconds = perf_counter() - start_counter - subsolutions = [ray.get(ref) for ref in refs] + solve_time_in_seconds = perf_counter() - start_counter result = concatenate(subsolutions).truncate(num_states) result.info["solve_time_in_seconds"] = solve_time_in_seconds return result From f50d506d291a82bdf2d67c724519288a9cd9919f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Thu, 27 Jul 2023 11:11:08 +0200 Subject: [PATCH 07/22] Add WIP implementation of ground-only sampler --- omnisolver/bruteforce/gpu/sampler.py | 30 ++++-- omnisolver/extensions/bruteforce_gpu.cu | 102 ++++++++++++++++-- omnisolver/extensions/bruteforce_gpu.h | 8 +- .../extensions/bruteforce_wrapper_gpu.pyx | 27 +++++ 4 files changed, 146 insertions(+), 21 deletions(-) diff --git a/omnisolver/bruteforce/gpu/sampler.py b/omnisolver/bruteforce/gpu/sampler.py index d26e8c4..0d0c798 100644 --- a/omnisolver/bruteforce/gpu/sampler.py +++ b/omnisolver/bruteforce/gpu/sampler.py @@ -4,7 +4,7 @@ import numpy as np from dimod import Sampler, SampleSet, Vartype -from omnisolver.bruteforce.ext.gpu import gpu_search +from omnisolver.bruteforce.ext.gpu import gpu_search, gpu_search_ground_only def _convert_int_to_sample(val, num_variables): @@ -56,15 +56,25 @@ def sample(self, bqm, num_states, suffix_size, grid_size, block_size, dtype=np.f start_counter = perf_counter() - gpu_search( - qubo_mat, - num_states, - states_out, - energies_out, - grid_size, - block_size, - suffix_size, - ) + if num_states == 1: # Shortcut if we are only looking for a ground state + gpu_search_ground_only( + qubo_mat, + states_out, + energies_out, + grid_size, + block_size, + suffix_size, + ) + else: + gpu_search( + qubo_mat, + num_states, + states_out, + energies_out, + grid_size, + block_size, + suffix_size, + ) solve_time_in_seconds = perf_counter() - start_counter diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index 03d62d1..b3a6575 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -24,6 +24,7 @@ inline void cuda_assert(cudaError_t code, const char *file, int line) } // Compute i-th Gray code. +__host__ __device__ uint64_t gray(uint64_t index) { return index ^ (index >> 1); } // Find First bit Set in a number. @@ -55,14 +56,22 @@ int ffs(uint64_t number) { template __device__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { int qi = (state >> i) & 1; // This is the i-th bit of state - T total = qubo_row[i]; // Start accumulating from the lineaer term - + T total = qubo_row[i]; // Start accumulating from the linear term + uint32_t low = state & 0xFFFFFFFF; + uint32_t high = state >> 32; // Go through each bit of state - for(int j = 0; j < N; j++) { + for(int j = 0; j < 32 && j < N; j++) { if(i != j) { // except the one to flip, it's already accounted for - total += qubo_row[j] * (state & 1); + total += qubo_row[j] * (low % 2); //state;// (state & 1); } - state >>= 1; // move to next bit + low >>= 1; // move to next bit + } + + for(int j=32; j < N; j++) { + if(i != j) { // except the one to flip, it's already accounted for + total += qubo_row[j] * (high % 2); + } + high >>= 1; // move to next bit } // When flipping from 0 to 1 there is nothing to do, otherwise we need @@ -88,7 +97,7 @@ __device__ void _init(T* qubo, int N, uint64_t* states, T* energies, uint64_t in // most significant bits of state to 011. while(suffix > 0) { // Find first set (note: not our function, this one is CUDA's intrinsic) - uint64_t bit_in_suffix = __ffs(suffix); + uint64_t bit_in_suffix = __ffsll(suffix); // Compute bit index from the most significant position // Since we reduce suffix in each iteration, we have to store the offset // travelled so far in the `offset` variable. @@ -145,6 +154,41 @@ __global__ void single_step( } } +// Kernel performing whole ground state (and only ground state) computation in a single step +template +__global__ void find_ground( + T* qubo, + int N, + T* energies, + uint64_t* states, + uint64_t suffix_size +) { + uint64_t chunk_size = 1 << suffix_size; + T tmp_energy, candidate_energy; + uint64_t tmp_state, candidate_state; + int bit_to_flip; + + for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + _init(qubo, N, states, energies, i); + candidate_energy = energies[i]; + tmp_energy = candidate_energy; + candidate_state = states[i]; + tmp_state = candidate_state; + for(uint64_t j=1; j < (1L << (N - suffix_size)); j++) { + bit_to_flip = __ffsll(gray(j) ^ gray(j - 1)) - 1; + tmp_energy = tmp_energy - energy_diff(qubo + N * bit_to_flip, N, tmp_state, bit_to_flip); + tmp_state = tmp_state ^ (1L << bit_to_flip); + if(tmp_energy < candidate_energy) { + candidate_energy = tmp_energy; + candidate_state = tmp_state; + } + } + states[i] = candidate_state; + energies[i] = candidate_energy; + } +} + + // Predicate used in thrust::copy_if // Given a tuple (state, energy), copy this copule iff energy < threshold. template @@ -214,7 +258,7 @@ void search( ); // Iterate in chunks in gray code order. - for(int i = 1; i < num_chunks; i++) { + for(uint64_t i = 1; i < num_chunks; i++) { // To compute bit to flip compute two consecutive gray codes and see at which // bit they differ. Subtract 1 because ffs counts from 1. int bit_to_flip = ffs(gray(i) ^ gray(i - 1)) - 1; @@ -281,3 +325,47 @@ template void search( int block_size, int suffix_size ); + +template +void search_ground_only( + T* qubo, + int N, + uint64_t* states_out, + T* energies_out, + int grid_size, + int block_size, + int suffix_size +) { + uint64_t chunk_size = 1L << suffix_size; + device_vector qubo_gpu(qubo, qubo + N * N); + + energy_vector energies(chunk_size); + state_vector states(chunk_size); + + find_ground<<>>((T*)qubo_gpu, N, (T*)energies, states, suffix_size); + cuda_error_check(cudaGetLastError()); + cudaDeviceSynchronize(); + thrust::sort_by_key(energies.begin(), energies.end(), states.begin()); + thrust::copy(states.begin(), states.begin() + 1, states_out); + thrust::copy(energies.begin(), energies.begin() + 1, energies_out); +} + +template void search_ground_only( + float* qubo, + int N, + uint64_t* states_out, + float* energies_out, + int grid_size, + int block_size, + int suffix_size +); + +template void search_ground_only( + double* qubo, + int N, + uint64_t* states_out, + double* energies_out, + int grid_size, + int block_size, + int suffix_size +); diff --git a/omnisolver/extensions/bruteforce_gpu.h b/omnisolver/extensions/bruteforce_gpu.h index 6b7ff56..0ee6e2d 100644 --- a/omnisolver/extensions/bruteforce_gpu.h +++ b/omnisolver/extensions/bruteforce_gpu.h @@ -11,12 +11,12 @@ void search( ); template -void search( +void search_ground_only( T* qubo, int N, - T* energies_out, uint64_t* states_out, - uint64_t num_states_in_chunk, + T* energies_out, int blocks_per_grid, - int threads_per_block + int threads_per_block, + int suffix_size ); diff --git a/omnisolver/extensions/bruteforce_wrapper_gpu.pyx b/omnisolver/extensions/bruteforce_wrapper_gpu.pyx index 307c4ef..21bce4a 100644 --- a/omnisolver/extensions/bruteforce_wrapper_gpu.pyx +++ b/omnisolver/extensions/bruteforce_wrapper_gpu.pyx @@ -21,6 +21,15 @@ cdef extern from "bruteforce_gpu.h": int suffix_size ) except+ + void search_ground_only[T]( + T* qubo, + int N, + uint64_t* states_out, + T* energies_out, + int blocks_per_grid, + int threads_per_block, + int suffix_size + ) except+ def gpu_search( real[:,:] qubo, @@ -41,3 +50,21 @@ def gpu_search( block_size, suffix_size ) + +def gpu_search_ground_only( + real[:,:] qubo, + uint64_t[::1] states_out, + real[::1] energies_out, + int grid_size, + int block_size, + int suffix_size +): + search_ground_only( + &qubo[0, 0], + qubo.shape[0], + &states_out[0], + &energies_out[0], + grid_size, + block_size, + suffix_size + ) From 39f8d5dad076ed50fd657014d039c3143514d4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Sun, 3 Sep 2023 16:02:49 +0200 Subject: [PATCH 08/22] Reduce number of variables used in energy_diff --- omnisolver/extensions/bruteforce_gpu.cu | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index b3a6575..b9635e1 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -54,24 +54,26 @@ int ffs(uint64_t number) { // One can easily verify that to compute the difference above we need only the // i-th row of QUBO, and hence we already pass this row instead of computign the offset. template -__device__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { +__device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { int qi = (state >> i) & 1; // This is the i-th bit of state T total = qubo_row[i]; // Start accumulating from the linear term - uint32_t low = state & 0xFFFFFFFF; - uint32_t high = state >> 32; + + uint32_t word = state & 0xFFFFFFFF; + // Go through each bit of state for(int j = 0; j < 32 && j < N; j++) { - if(i != j) { // except the one to flip, it's already accounted for - total += qubo_row[j] * (low % 2); //state;// (state & 1); + if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + total += qubo_row[j]; } - low >>= 1; // move to next bit + word /= 2; //>>= 1; // move to next bit } + word = state >> 32; for(int j=32; j < N; j++) { - if(i != j) { // except the one to flip, it's already accounted for - total += qubo_row[j] * (high % 2); + if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + total += qubo_row[j]; } - high >>= 1; // move to next bit + word /= 2; // move to next bit } // When flipping from 0 to 1 there is nothing to do, otherwise we need From 90c877717a4cc278dbf86b528a9f53aa0dace576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 4 Sep 2023 01:08:55 +0200 Subject: [PATCH 09/22] Implement partial diff buffer optimization --- omnisolver/bruteforce/gpu/distributed.py | 20 +++- omnisolver/bruteforce/gpu/sampler.py | 13 ++- omnisolver/extensions/bruteforce_gpu.cu | 116 +++++++++++++++++++++-- omnisolver/extensions/bruteforce_gpu.h | 3 +- 4 files changed, 140 insertions(+), 12 deletions(-) diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py index 1f0580d..9d3a873 100644 --- a/omnisolver/bruteforce/gpu/distributed.py +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -22,7 +22,15 @@ def _solve_subproblem(bqm, num_states, fixed_vars, suffix_size, grid_size, block class DistributedBruteforceGPUSampler(Sampler): def sample( - self, bqm, num_states, num_fixed_vars, suffix_size, grid_size, block_size, dtype=np.float32 + self, + bqm, + num_states, + num_fixed_vars, + suffix_size, + grid_size, + block_size, + partial_diff_buffer_depth=1, + dtype=np.float32, ): if bqm.vartype == Vartype.SPIN: return self.sample( @@ -32,6 +40,7 @@ def sample( suffix_size, grid_size, block_size, + partial_diff_buffer_depth, ).change_vartype("SPIN", inplace=False) bqm, mapping = bqm.relabel_variables_as_integers() @@ -44,7 +53,14 @@ def sample( refs = [ _solve_subproblem.remote( - bqm, num_states, fixed_vars, suffix_size, grid_size, block_size, dtype + bqm, + num_states, + fixed_vars, + suffix_size, + grid_size, + block_size, + partial_diff_buffer_depth, + dtype, ) for fixed_vars in subproblems ] diff --git a/omnisolver/bruteforce/gpu/sampler.py b/omnisolver/bruteforce/gpu/sampler.py index 0d0c798..2957052 100644 --- a/omnisolver/bruteforce/gpu/sampler.py +++ b/omnisolver/bruteforce/gpu/sampler.py @@ -16,7 +16,16 @@ def _convert_int_to_sample(val, num_variables): class BruteforceGPUSampler(Sampler): - def sample(self, bqm, num_states, suffix_size, grid_size, block_size, dtype=np.float32): + def sample( + self, + bqm, + num_states, + suffix_size, + grid_size, + block_size, + partial_diff_buff_depth=1, + dtype=np.float32, + ): """Solve Binary Quadratic Model using exhaustive (bruteforce) search on the GPU. :param bqm: Binary Quadratic Model instance to solve. @@ -38,6 +47,7 @@ def sample(self, bqm, num_states, suffix_size, grid_size, block_size, dtype=np.f suffix_size, grid_size, block_size, + partial_diff_buff_depth, ).change_vartype("SPIN", inplace=False) bqm, mapping = bqm.relabel_variables_as_integers() @@ -64,6 +74,7 @@ def sample(self, bqm, num_states, suffix_size, grid_size, block_size, dtype=np.f grid_size, block_size, suffix_size, + partial_diff_buff_depth, ) else: gpu_search( diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index b9635e1..472658f 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -81,6 +81,34 @@ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int return (2 * qi - 1) * total; } +template +__device__ __forceinline__ T energy_diff_reduced(T* qubo_row, int N, int offset, uint64_t state, int i, T common_part) { + int qi = (state >> i) & 1; // This is the i-th bit of state + T total = qubo_row[i] + common_part; // Start accumulating from the linear term + + uint32_t word = state & 0xFFFFFFFF; + uint32_t limit = N - offset; + // Go through each bit of state + for(int j = 0; j < 32 && j < limit; j++) { + if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + total += qubo_row[j]; + } + word /= 2; //>>= 1; // move to next bit + } + + word = state >> 32; + for(int j=32; j < limit; j++) { + if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + total += qubo_row[j]; + } + word /= 2; // move to next bit + } + + // When flipping from 0 to 1 there is nothing to do, otherwise we need + // to multiply the total by -1. + return (2 * qi - 1) * total; +} + // Initialize first states and energies for further processing. // The idea is as follows. Each state (which is N-bit number) is (logically) split into // two parts: @@ -163,22 +191,42 @@ __global__ void find_ground( int N, T* energies, uint64_t* states, - uint64_t suffix_size + uint64_t suffix_size, + T* partial_diff_buffer, + int partial_diff_buffer_depth ) { - uint64_t chunk_size = 1 << suffix_size; + uint64_t chunk_size = 1L << suffix_size; T tmp_energy, candidate_energy; uint64_t tmp_state, candidate_state; int bit_to_flip; + T block_energy_part = 0.0; for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { - _init(qubo, N, states, energies, i); candidate_energy = energies[i]; tmp_energy = candidate_energy; candidate_state = states[i]; tmp_state = candidate_state; for(uint64_t j=1; j < (1L << (N - suffix_size)); j++) { bit_to_flip = __ffsll(gray(j) ^ gray(j - 1)) - 1; - tmp_energy = tmp_energy - energy_diff(qubo + N * bit_to_flip, N, tmp_state, bit_to_flip); + + if(bit_to_flip < partial_diff_buffer_depth) { + tmp_energy = tmp_energy - energy_diff_reduced( + qubo + N * bit_to_flip, + N, + suffix_size, + tmp_state, + bit_to_flip, + partial_diff_buffer[bit_to_flip * chunk_size + i] + ); + tmp_energy = tmp_energy; + } else { + tmp_energy = tmp_energy - energy_diff( + qubo + N * bit_to_flip, + N, + tmp_state, + bit_to_flip + ); + } tmp_state = tmp_state ^ (1L << bit_to_flip); if(tmp_energy < candidate_energy) { candidate_energy = tmp_energy; @@ -191,6 +239,37 @@ __global__ void find_ground( } +template +__global__ void _initialize_partial_diff_buffer( + T* qubo, + int N, + uint64_t* states, + uint64_t suffix_size, + T* partial_diff_buffer, + int partial_diff_buffer_depth +) { + uint64_t chunk_size = 1L << suffix_size; + uint64_t shifted_state, tmp_state; + T total; + T* qubo_row; + + for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + shifted_state = states[i] >> (N - suffix_size); + for(int bit_to_flip=0; bit_to_flip < partial_diff_buffer_depth; bit_to_flip++) { + total = 0; + tmp_state = shifted_state; + qubo_row = qubo + N * bit_to_flip; + for(int k=N-suffix_size; k < N; k++) { + if(tmp_state % 2) total += qubo_row[k]; + tmp_state /= 2; + } + + partial_diff_buffer[bit_to_flip * chunk_size + i] = total; + } + } +} + + // Predicate used in thrust::copy_if // Given a tuple (state, energy), copy this copule iff energy < threshold. template @@ -336,7 +415,8 @@ void search_ground_only( T* energies_out, int grid_size, int block_size, - int suffix_size + int suffix_size, + int partial_diff_buffer_depth ) { uint64_t chunk_size = 1L << suffix_size; device_vector qubo_gpu(qubo, qubo + N * N); @@ -344,7 +424,25 @@ void search_ground_only( energy_vector energies(chunk_size); state_vector states(chunk_size); - find_ground<<>>((T*)qubo_gpu, N, (T*)energies, states, suffix_size); + energy_vector partial_diff_buffer(partial_diff_buffer_depth * chunk_size); + + init<<>>((T*)qubo_gpu, N, (T*)energies, states, chunk_size); + cuda_error_check(cudaGetLastError()); + cudaDeviceSynchronize(); + + _initialize_partial_diff_buffer<<>>((T*)qubo_gpu, N, states, suffix_size, (T*) partial_diff_buffer, partial_diff_buffer_depth); + cuda_error_check(cudaGetLastError()); + cudaDeviceSynchronize(); + + find_ground<<>>( + (T*)qubo_gpu, + N, + (T*)energies, + states, + suffix_size, + (T*)partial_diff_buffer, + partial_diff_buffer_depth + ); cuda_error_check(cudaGetLastError()); cudaDeviceSynchronize(); thrust::sort_by_key(energies.begin(), energies.end(), states.begin()); @@ -359,7 +457,8 @@ template void search_ground_only( float* energies_out, int grid_size, int block_size, - int suffix_size + int suffix_size, + int partial_diff_buffer_depth ); template void search_ground_only( @@ -369,5 +468,6 @@ template void search_ground_only( double* energies_out, int grid_size, int block_size, - int suffix_size + int suffix_size, + int partial_diff_buffer_depth ); diff --git a/omnisolver/extensions/bruteforce_gpu.h b/omnisolver/extensions/bruteforce_gpu.h index 0ee6e2d..8c9349c 100644 --- a/omnisolver/extensions/bruteforce_gpu.h +++ b/omnisolver/extensions/bruteforce_gpu.h @@ -18,5 +18,6 @@ void search_ground_only( T* energies_out, int blocks_per_grid, int threads_per_block, - int suffix_size + int suffix_size, + int partial_diff_buff_depth ); From 021434621a9815139f3004cd5cb167a21b1bc87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 4 Sep 2023 23:46:14 +0200 Subject: [PATCH 10/22] Implement computing prefix part of energy diff on CPU --- omnisolver/bruteforce/gpu/sampler.py | 4 + omnisolver/extensions/bruteforce_gpu.cu | 151 ++++++++++++------ omnisolver/extensions/bruteforce_gpu.h | 1 + .../extensions/bruteforce_wrapper_gpu.pyx | 12 +- 4 files changed, 119 insertions(+), 49 deletions(-) diff --git a/omnisolver/bruteforce/gpu/sampler.py b/omnisolver/bruteforce/gpu/sampler.py index 2957052..d32c554 100644 --- a/omnisolver/bruteforce/gpu/sampler.py +++ b/omnisolver/bruteforce/gpu/sampler.py @@ -23,6 +23,7 @@ def sample( suffix_size, grid_size, block_size, + num_steps_per_kernel=16, partial_diff_buff_depth=1, dtype=np.float32, ): @@ -47,7 +48,9 @@ def sample( suffix_size, grid_size, block_size, + num_steps_per_kernel, partial_diff_buff_depth, + dtype, ).change_vartype("SPIN", inplace=False) bqm, mapping = bqm.relabel_variables_as_integers() @@ -74,6 +77,7 @@ def sample( grid_size, block_size, suffix_size, + num_steps_per_kernel, partial_diff_buff_depth, ) else: diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index 472658f..f6cec70 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -54,7 +54,7 @@ int ffs(uint64_t number) { // One can easily verify that to compute the difference above we need only the // i-th row of QUBO, and hence we already pass this row instead of computign the offset. template -__device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { +__host__ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { int qi = (state >> i) & 1; // This is the i-th bit of state T total = qubo_row[i]; // Start accumulating from the linear term @@ -84,23 +84,19 @@ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int template __device__ __forceinline__ T energy_diff_reduced(T* qubo_row, int N, int offset, uint64_t state, int i, T common_part) { int qi = (state >> i) & 1; // This is the i-th bit of state - T total = qubo_row[i] + common_part; // Start accumulating from the linear term + T total = common_part; // Start accumulating from the linear term + + uint32_t word = (state >> offset) & 0xFFFFFFFF; - uint32_t word = state & 0xFFFFFFFF; - uint32_t limit = N - offset; // Go through each bit of state - for(int j = 0; j < 32 && j < limit; j++) { - if(i != j && (word % 2)) { // except the one to flip, it's already accounted for - total += qubo_row[j]; - } + for(int j = offset; j < 32 + offset && j < N; j++) { + if(word % 2) total += qubo_row[j]; word /= 2; //>>= 1; // move to next bit } - word = state >> 32; - for(int j=32; j < limit; j++) { - if(i != j && (word % 2)) { // except the one to flip, it's already accounted for - total += qubo_row[j]; - } + word = state >> (32+offset); + for(int j=32+offset; j < N; j++) { + if(word % 2) total += qubo_row[j]; word /= 2; // move to next bit } @@ -184,6 +180,13 @@ __global__ void single_step( } } + +template +__device__ __forceinline__ T* row_ptr(T* array_2d, uint64_t n_cols, uint64_t row_id) { + return array_2d + n_cols * row_id; +} + + // Kernel performing whole ground state (and only ground state) computation in a single step template __global__ void find_ground( @@ -191,7 +194,12 @@ __global__ void find_ground( int N, T* energies, uint64_t* states, + T* best_energies, + uint64_t* best_states, uint64_t suffix_size, + uint64_t num_iterations, + int* bit_buffer, + T* prefix_diff_buffer, T* partial_diff_buffer, int partial_diff_buffer_depth ) { @@ -199,42 +207,42 @@ __global__ void find_ground( T tmp_energy, candidate_energy; uint64_t tmp_state, candidate_state; int bit_to_flip; - T block_energy_part = 0.0; + int qi; for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { - candidate_energy = energies[i]; - tmp_energy = candidate_energy; - candidate_state = states[i]; - tmp_state = candidate_state; - for(uint64_t j=1; j < (1L << (N - suffix_size)); j++) { - bit_to_flip = __ffsll(gray(j) ^ gray(j - 1)) - 1; - + candidate_energy = best_energies[i]; + tmp_energy = energies[i]; + candidate_state = best_states[i]; + tmp_state = states[i]; + for(uint64_t j=0; j < num_iterations; j++) { + bit_to_flip = bit_buffer[j]; if(bit_to_flip < partial_diff_buffer_depth) { - tmp_energy = tmp_energy - energy_diff_reduced( - qubo + N * bit_to_flip, - N, - suffix_size, - tmp_state, - bit_to_flip, - partial_diff_buffer[bit_to_flip * chunk_size + i] + qi = (tmp_state >> bit_to_flip) & 1; + tmp_energy = tmp_energy - (2 * qi - 1) * ( + row_ptr(partial_diff_buffer, chunk_size, bit_to_flip)[i] + + prefix_diff_buffer[j] ); - tmp_energy = tmp_energy; } else { - tmp_energy = tmp_energy - energy_diff( - qubo + N * bit_to_flip, + tmp_energy -= energy_diff_reduced( + row_ptr(qubo, N, bit_to_flip), N, + N-suffix_size, tmp_state, - bit_to_flip + bit_to_flip, + prefix_diff_buffer[j] ); } + tmp_state = tmp_state ^ (1L << bit_to_flip); if(tmp_energy < candidate_energy) { candidate_energy = tmp_energy; candidate_state = tmp_state; } } - states[i] = candidate_state; - energies[i] = candidate_energy; + states[i] = tmp_state; + energies[i] = tmp_energy; + best_states[i] = candidate_state; + best_energies[i] = candidate_energy; } } @@ -416,6 +424,7 @@ void search_ground_only( int grid_size, int block_size, int suffix_size, + int num_steps_per_kernel, int partial_diff_buffer_depth ) { uint64_t chunk_size = 1L << suffix_size; @@ -423,31 +432,79 @@ void search_ground_only( energy_vector energies(chunk_size); state_vector states(chunk_size); + energy_vector best_energies(chunk_size); + state_vector best_states(chunk_size); + device_vector bit_flip_buffer(num_steps_per_kernel); + energy_vector prefix_diff_buffer(num_steps_per_kernel); energy_vector partial_diff_buffer(partial_diff_buffer_depth * chunk_size); + std::vector src_bit_flip_buffer(num_steps_per_kernel); + std::vector src_prefix_diff_buffer(num_steps_per_kernel); + + uint64_t last_gray_code, next_gray_code; + uint64_t counter; + int64_t base_state; + T base_energy; + init<<>>((T*)qubo_gpu, N, (T*)energies, states, chunk_size); cuda_error_check(cudaGetLastError()); cudaDeviceSynchronize(); + best_energies = energies; + best_states = states; + _initialize_partial_diff_buffer<<>>((T*)qubo_gpu, N, states, suffix_size, (T*) partial_diff_buffer, partial_diff_buffer_depth); cuda_error_check(cudaGetLastError()); cudaDeviceSynchronize(); - find_ground<<>>( - (T*)qubo_gpu, - N, - (T*)energies, - states, - suffix_size, - (T*)partial_diff_buffer, - partial_diff_buffer_depth - ); + counter = 0; + last_gray_code = 0; + + base_energy = 0; + base_state = 0; + for(uint64_t i = 0; i < (1L << (N - suffix_size)) / num_steps_per_kernel; i++) { + for(uint64_t j = 0; j < num_steps_per_kernel; j++) { + counter += 1; + next_gray_code = gray(counter); + int bit_to_flip = ffs(last_gray_code ^ next_gray_code)-1; + src_bit_flip_buffer[j] = bit_to_flip; + src_prefix_diff_buffer[j] = qubo[(N + 1) * bit_to_flip]; + for(int k = 0; k < N - suffix_size; k++) { + if(((base_state >> k) % 2) && k != bit_to_flip) { + src_prefix_diff_buffer[j] += qubo[N * bit_to_flip + k]; + } + } + + last_gray_code = next_gray_code; + base_state ^= (1L << src_bit_flip_buffer[j]); + } + bit_flip_buffer = src_bit_flip_buffer; + prefix_diff_buffer = src_prefix_diff_buffer; + cuda_error_check(cudaGetLastError()); + cudaDeviceSynchronize(); + find_ground<<>>( + (T*)qubo_gpu, + N, + (T*)energies, + states, + (T*)best_energies, + best_states, + suffix_size, + num_steps_per_kernel, + (int*) bit_flip_buffer, + (T*) prefix_diff_buffer, + (T*)partial_diff_buffer, + partial_diff_buffer_depth + ); + + } + cuda_error_check(cudaGetLastError()); cudaDeviceSynchronize(); - thrust::sort_by_key(energies.begin(), energies.end(), states.begin()); - thrust::copy(states.begin(), states.begin() + 1, states_out); - thrust::copy(energies.begin(), energies.begin() + 1, energies_out); + thrust::sort_by_key(best_energies.begin(), best_energies.end(), best_states.begin()); + thrust::copy(best_states.begin(), best_states.begin() + 1, states_out); + thrust::copy(best_energies.begin(), best_energies.begin() + 1, energies_out); } template void search_ground_only( @@ -458,6 +515,7 @@ template void search_ground_only( int grid_size, int block_size, int suffix_size, + int num_steps_per_kernel, int partial_diff_buffer_depth ); @@ -469,5 +527,6 @@ template void search_ground_only( int grid_size, int block_size, int suffix_size, + int num_steps_per_kernel, int partial_diff_buffer_depth ); diff --git a/omnisolver/extensions/bruteforce_gpu.h b/omnisolver/extensions/bruteforce_gpu.h index 8c9349c..d424c47 100644 --- a/omnisolver/extensions/bruteforce_gpu.h +++ b/omnisolver/extensions/bruteforce_gpu.h @@ -19,5 +19,6 @@ void search_ground_only( int blocks_per_grid, int threads_per_block, int suffix_size, + int num_steps_per_kernel, int partial_diff_buff_depth ); diff --git a/omnisolver/extensions/bruteforce_wrapper_gpu.pyx b/omnisolver/extensions/bruteforce_wrapper_gpu.pyx index 21bce4a..e0291b4 100644 --- a/omnisolver/extensions/bruteforce_wrapper_gpu.pyx +++ b/omnisolver/extensions/bruteforce_wrapper_gpu.pyx @@ -28,7 +28,9 @@ cdef extern from "bruteforce_gpu.h": T* energies_out, int blocks_per_grid, int threads_per_block, - int suffix_size + int suffix_size, + int num_steps_per_kernel, + int partial_diff_buff_depth ) except+ def gpu_search( @@ -57,7 +59,9 @@ def gpu_search_ground_only( real[::1] energies_out, int grid_size, int block_size, - int suffix_size + int suffix_size, + int num_steps_per_kernel=16, + int partial_diff_buffer_depth=1 ): search_ground_only( &qubo[0, 0], @@ -66,5 +70,7 @@ def gpu_search_ground_only( &energies_out[0], grid_size, block_size, - suffix_size + suffix_size, + num_steps_per_kernel, + partial_diff_buffer_depth ) From 72c76a8e6b45e1c2d751a906ee97f205a529a99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Tue, 5 Sep 2023 00:00:39 +0200 Subject: [PATCH 11/22] Fix _solve_subproblems signature --- omnisolver/bruteforce/gpu/distributed.py | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py index 9d3a873..1d50a29 100644 --- a/omnisolver/bruteforce/gpu/distributed.py +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -10,12 +10,31 @@ @ray.remote(num_gpus=1) -def _solve_subproblem(bqm, num_states, fixed_vars, suffix_size, grid_size, block_size, dtype): +def _solve_subproblem( + bqm, + num_states, + fixed_vars, + suffix_size, + grid_size, + block_size, + num_steps_per_kernel, + partial_diff_buffer_depth, + dtype, +): new_bqm = bqm.copy() new_bqm.fix_variables(fixed_vars) sampler = BruteforceGPUSampler() - result = sampler.sample(new_bqm, num_states, suffix_size, grid_size, block_size, dtype) + result = sampler.sample( + new_bqm, + num_states, + suffix_size, + grid_size, + block_size, + num_steps_per_kernel, + partial_diff_buffer_depth, + dtype, + ) return append_variables(result, fixed_vars) @@ -29,6 +48,7 @@ def sample( suffix_size, grid_size, block_size, + num_steps_per_kernel=1, partial_diff_buffer_depth=1, dtype=np.float32, ): @@ -40,6 +60,7 @@ def sample( suffix_size, grid_size, block_size, + num_steps_per_kernel, partial_diff_buffer_depth, ).change_vartype("SPIN", inplace=False) @@ -59,6 +80,7 @@ def sample( suffix_size, grid_size, block_size, + num_steps_per_kernel, partial_diff_buffer_depth, dtype, ) From 1da7b4828f6919cb3988a9b572818bcd5128b774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Thu, 7 Sep 2023 22:10:43 +0200 Subject: [PATCH 12/22] Make all iteration variables 64 bit --- omnisolver/extensions/bruteforce_gpu.cu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index f6cec70..87577ec 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -152,7 +152,7 @@ __global__ void init( uint64_t* states, uint64_t states_in_chunk ) { - for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { + for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { _init(qubo, N, states, energies, i); } } @@ -172,7 +172,7 @@ __global__ void single_step( ) { T* qubo_row = qubo + N * bit_to_flip; - for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { + for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { uint64_t state = states[i]; T energy = energies[i]; energies[i] = energy - energy_diff(qubo_row, N, state, bit_to_flip); @@ -209,7 +209,7 @@ __global__ void find_ground( int bit_to_flip; int qi; - for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { candidate_energy = best_energies[i]; tmp_energy = energies[i]; candidate_state = best_states[i]; @@ -261,7 +261,7 @@ __global__ void _initialize_partial_diff_buffer( T total; T* qubo_row; - for(int i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { shifted_state = states[i] >> (N - suffix_size); for(int bit_to_flip=0; bit_to_flip < partial_diff_buffer_depth; bit_to_flip++) { total = 0; From c7729c3087f009a34142d05d76922873ac80edcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Fri, 8 Sep 2023 11:28:37 +0000 Subject: [PATCH 13/22] Fix typo in non-distributed sampler --- omnisolver/bruteforce/gpu/sampler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/omnisolver/bruteforce/gpu/sampler.py b/omnisolver/bruteforce/gpu/sampler.py index d32c554..81a960c 100644 --- a/omnisolver/bruteforce/gpu/sampler.py +++ b/omnisolver/bruteforce/gpu/sampler.py @@ -24,7 +24,7 @@ def sample( grid_size, block_size, num_steps_per_kernel=16, - partial_diff_buff_depth=1, + partial_diff_buffer_depth=1, dtype=np.float32, ): """Solve Binary Quadratic Model using exhaustive (bruteforce) search on the GPU. @@ -49,7 +49,7 @@ def sample( grid_size, block_size, num_steps_per_kernel, - partial_diff_buff_depth, + partial_diff_buffer_depth, dtype, ).change_vartype("SPIN", inplace=False) @@ -78,7 +78,7 @@ def sample( block_size, suffix_size, num_steps_per_kernel, - partial_diff_buff_depth, + partial_diff_buffer_depth, ) else: gpu_search( From 14c9fd8f866cef38de6d658b16363bd0c30d3d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:52:53 +0100 Subject: [PATCH 14/22] Update docs --- docs/Makefile | 20 ---- docs/_templates/autoapi/index.rst | 13 --- docs/assets/logo-large.png | Bin 0 -> 114001 bytes docs/assets/logo.png | Bin 0 -> 59058 bytes docs/conf.py | 55 --------- docs/index.md | 110 ++++++++++++------ docs/javascripts/mathjax.js | 18 +++ docs/make.bat | 35 ------ .../omnisolver_bruteforce_distributed.md | 2 + .../omnisolver_bruteforce_sampler.md | 2 + docs/requirements.txt | 5 - mkdocs.yml | 66 +++++++++++ 12 files changed, 164 insertions(+), 162 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/_templates/autoapi/index.rst create mode 100644 docs/assets/logo-large.png create mode 100644 docs/assets/logo.png delete mode 100644 docs/conf.py create mode 100644 docs/javascripts/mathjax.js delete mode 100644 docs/make.bat create mode 100644 docs/reference/omnisolver_bruteforce_distributed.md create mode 100644 docs/reference/omnisolver_bruteforce_sampler.md delete mode 100644 docs/requirements.txt create mode 100644 mkdocs.yml diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cb..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_templates/autoapi/index.rst b/docs/_templates/autoapi/index.rst deleted file mode 100644 index 87a7ff9..0000000 --- a/docs/_templates/autoapi/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -API Reference -============= - -This page contains auto-generated API reference documentation. - -.. toctree:: - :titlesonly: - - {% for page in pages | sort %} - {% if (page.top_level_object or page.name.split('.') | length == 3) and page.display %} - {{ page.short_name | capitalize }} <{{ page.include_path }}> - {% endif %} - {% endfor %} diff --git a/docs/assets/logo-large.png b/docs/assets/logo-large.png new file mode 100644 index 0000000000000000000000000000000000000000..2d16045ef6d12af5f0b0dcb6baf165729fce185c GIT binary patch literal 114001 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 z zu2Kd9s4`~Lb6(A0vn*!?F$Nz`v z8^_v_=nmA@TB=tEq_HFIcSdE79{un`5aG;VC6vG~m!loxA(NTb!_;Jw8;?RI`~$@1 zfVOV_dnS2})DA@`qD*s@E_58>5ofWxLVhiYLw8(byp3^srTWJFGV(I@^f&%oAvd$G zw79@Vla0_uh20hA)WHwzd|eDUzd3;HND<`r#Si|jfc8_;UKleQIJkjLtHFk|rbGP9 zHl}WP6gN;sdPqZkb?(n7*#x8jREiYyVa3X+N+R5za!#@NmvKs;L6Y5psK=viC9)@tYFWehsdG*EXT|jjTANA3?DJV1PiSfI2p7TbfR!c|&(9_JH5S zBFl2YJMdWJLG{bhE;d0}0=&`*rCSMBY*ghe#ILR(3c2X~bBq`>Vv?^0GJ@@g1tl$ywvIHw@%mvJ6v zd=1Ym@2|LLfrmQn5-6aOkRRq9@Jxl#KK`Gf1_f`<)Jf)#>|d~|9T6Q*IAw~@BN|2Y zFNEEU65u&~Jc(sT?HiDi6@V_$S~EazKA%=zO@9Qy7*z2Qus^B93T|hM834x5TxSlV zws>KZ+0%XB+kF@i-)7obCBQ!VkC=R|ob6i?(bL6$(r5dUgcY$OiI&$7{-8gTf+7sU zIL&SmSV_FKPcV_>ZHBkI*z~%?i2E~OzQD>8aKro_;6-hxw!`TWx&f8Z6ye=>iJs*J zZ1Q+Oh$gY8H^iwz15O3sf4f`YKlgTUtheSW>YEsEF&#*_>6RB@7_+~s8`mRA9bwn@ zyb~bqU^{HFCz^Is$WN^Bj1Pa4wX8haAv@riBHTp044ohn==6(CowwAJcga9heP)L5 zU+q8Tgdz!K7*M)i+&Lb?e-+<6>Rbqi~4y*8))V%y+AmIG{47+l$bu*A^2<8-bLj&T;J1nuw)E$zpnbPajn7sL%E?#e-L?2CxjA z(4xt>fT<9u2{e?PLoGx@$NFcGh9tV;xW$ZwOu-fHde}c?vg+^VAlns&d+84kI2XJ* z>n*XxD!bK2ARCf;fj|B1Pd(a?38?hykl5fp3QhZ)4U_-y*|b`Wn-JT^g3O|k&ptBz6P-S3OEV_rvl^F> z$CqsJv6q{|3(suP34J*btK4&;LkPL3vg#Jmux%wy^R4+k#d+0x4?l3r7A}1eOTb9x z{jC)`JwH$RNh`&9rvQ0T1LYV=7z+*u8v<0RBO17#4QT11y}H{8fq$#vqQEsNWGlzt zaE&oZBw>NZ`V0+3*&PK}h2WI8zK-J|eCSMnLQR?!w2pFYz1eE2(hKBL4xtQje@Yzf z<=6Z(!a+xa;Osnm0B)eHxLtV08M>EpTrk@*pznC!c);D>4z@#~EZQT$@Bms6`ciIX zI=z^Tjifnfw|C~GJ*iRyPcPYjW$Tx(g=m^I^Wn zbH)vX+=QHn}8x zD=dPP07>|)EengX9tDMs$JyfUg5%(S4~`>B&bI0t%F{^5KiboE{?%ad;U4S9)nDGK z9Y!_97b-DI{{xIXTWzfs#RFerkCOXILqeZp)!1y0l4nSZW+;6#Ja)T~mRC3HjyTvY z%mZ%VNT$;O0jI61+#`k3?|GJG1e6U*Uj%JcYdr%cmpM38%d>#6r=gy;<3Vwc#Fe8j zNyT6Q81kFsp+@B%0;Sd2j|j&o3@i6o$`}ZvnZjsYmR{MxAt|emsp@KPsSk7qY>KSP ze5rFP>Fb5^XTh2JnKu@D8>62Z?kB0bc`Pq6xMfifK74QAGnF3KwwO+nmjYFK4gVU&TY)qZ}$v9hY{DJWN+G5mf#(!|K0s0l>*;%ueJPMW(?p^Y31-D0{k z8EiZ}tdd?2MoA>Vn&K>X^yu_7_(vbon1}&ZWJCnLWV5uOq^M45p^g>Ux;-bs-FwAA z+PldVBtW5ZHW#v`)#oBt%=c~ozaDM?J^X5`RZnE;2uqitE*kBx_SR`&9!ga_-)VZKA-#Q4s}cT?!Vi2L_R*R z(|UH}-+u~4)G5hjkNophF0+m{zAW^Y^EFztx%b?RYqP_CHP!K@A)NcLc>2u3G9pm3 z)p+7ABZOam5w?4rK z%}(v>QImtD{5K><+s-7`%G{(knXELq3;#$`Q&WTgx!+}^j?=+B-=3Nu(I~hq2>xo$3VLCX3=&qP{jKzHj}Wg;_GQeY$kc zzpRTwF&^(Gv^%wYyzUJo7d*i=pINkwGt))(F-(Tt%Jyt#YWN2_Z$R{&3Y)C)HFx5zyfGv%&r3@(!lF}jFD)nq{rmU~ zCcwY+eHAWcrbm9~9yV$UdfMqSWfop=N41)S-+-WAAU^e1PRP1kz^#1jIh?5KWwQ1K zNpDy$_;S>bDfYufcc!%0Y}i{ViO=O-ptTNik+=n!->0T#K)^N8s*Htz8TYbyjhFq&dWUCsF>T<;aql^lt(@z*@Gt^OxtD zCDF}FN8p2owOf$8$ z#b9+dBy>A7_9^d?zTG}^4{!h1$*vC8Py$FGDG@(r-mbZ#tK*l^G6uEdHg;l&o_#1c zgPB5o`vlSI6AUqjQun`4Z~#P{$n~X6LlT+7zsaDkGbig7p=RNHj>tBqiB!T4pES&J zZJ*k|e*qKn1MQ-|d2?YA`KUS{TXQ5hO`q+ga$Bt3!^YG;nfAjcmc38Wcla!3G5A`T zFT4I5(^lWXa{`7*Kg(8fmy6Nw;8b>%yv`>@uESEcaWu1xx#y@DTVnlmc6M?4d(&UP zdE*?I12yA4o{51O?t8MTd)$b4oY;1zNkiNpf5v-VXJT7>1H1Q`xBJ`d~#=ZGWa@?!|Tg2+ud_&zP;44P#wreE)q9 zOnMn6T@p-;nDBO-M0YW1;rg3|ziA=Y4xHL!72w6{#y3_e!;4gDU*oi%#Kn_Q3ql0z~1dJY2o-fZZ^Z0fw{>oHs0FI zM8F}xJY<`jpTV-8s?5itT+b!HH{t*R_#|{O>7vr;dQWWpH5Z1-R9EP6es00kN@ixf z{8EG`p^g4t=ciZu&#Ov~vka2=&*l0<&*U7tXDrZZHphxvT6GYq{*NdI(}Q~<;JY^t zw^CaD={+IDYRwpeFR!|=3X$dd#Ak!f0@f$Id7iKHIU)OCf*Jpt6Y6A1Y}ZbP#pC>a ztA3|fUOg=*x_%c% z$K=hbR%ozv#`D`9i>^YgF&v-COYr61zcwJ<8QFPLAnM>NI{enhjoC_`{r*dXsAX5~_5h@Cbq`y3o+gN6HKwvww|3fKL(+MqXTsW0* zXM(w}I>i@dBZ^3$gRiW0fj)2tF)Y0le-|_GxG}|}tNQKx3K6IEwfxq{j>~UA=qJ}W zE99UWD9-t>@BEnYL3{XJ8o!AgbXsqA@spiw{w21O;GJtHTXh+Rw{!n;eQw**eTv2d zs`asA-PSD#K60)4oZrM6xey4Wk=7C7E73o`^!203yJs5m5W;?Wq!d}}_6Ivt#{0almd_a807k0MohHKM0C8$O%BhF>Cr z0~BRVeIa zl`(l|9zDTu_%ZjiUcSOHX6XWPO6APjg~FdCj;{J|pA3gj@_-sAXe-M?;0ev%D8`JU-`V!7d!U8Jm&)wCaz=m3jJg2jCNo5Ir|-1^i=r;IVq zc1m3sY98Q-`NZUgA?Rzfh5*6y@^3od=L@6EJsrx0;T@P`RCY)P{MRB5;?_#0FO>DoZKX_V6xo8X z4}4i?g)HM`ugBrvMA*j?VJ;4S`0{ECME}IVr78Zm?UqV=MAmvKI?Hkm*L8X0wV##& zE+Mke&HwkdXn32;I(S>=D*6c9f_(h@w~@uNezFgKl22lRq}Pb)`QOBFgk@M~xLsFZ zH8#NyeS1`Y_;^#-GW^C&6vxx|A*nWGNA-wEQc$bFJcD~)n7agX=ut;mI$@$-g^%aJdF#*5REgRXp8~HGKpS|Bn{yo?W|c^B!v9N{A0F)2gM<|Db1CQ)1J=Mhict{x)7GDRe9K z@CHQpHy@H4X$w(Z%D~o;!USuve{FdzKdbhM$u5Qk>PzaUpum;XKF;LJi2c3aW>oQ# zHEDnyvAms1tf0jkf4Tmf`=8R}rsa+h@IAV}kDs-8k?y5T@t6NmcV_IfX^REvwec9y zz=0CqzAzuFd~75i#L0b2v=h~u+E*{UcKyCz0u?m1-OMCcLVyReCV^-9|9=E-!}Kux zXh1X*f&}i+sv-RnZy-_w!Y*k;G5iBq%Kjt?`M!gFO+JG>W!cqj<$1xnszewO?`!1y z=HGszdyR#TN)p+bWn}>DvZIV#n>HX>d)rk;3Y?eD+Yy{x;lffG_q>(q(UH-i`(>lX zU+}Zp%*zDl<9c!hGvC9z)7AS8x~C6~*s=1dyaD7zIWK3IzEB>=?aO?!uBaQ0j zhKt_flak-F&gVT}M6u=92cAM5=|(mzej&wn>_6SpYRK@NhmUfciBgn8z7K z3GjE5?|z?qSbbkwWxytxVa8QQ!C`47>6H2Jb+jUS;V8;H-}I=?FBEt|(p{~+AfD{jVSGYWLy^@#p1RhHmoKpp}% z^yxI;Pg&8$MwI&9NDBX0ah$Zb_{*Ye_6KbPD--Dpj7jIPp0t4Sa}t?Q{OT zlNQaKFW#huEIt#jDTFjS?JjEA!4QG+SxU+gp}Q{4E3o zf3`lDgX}g`?pd^{F)@51nN-jr4=Q>Lr(AxEXW z1=lwdUNm@HOB=VWj9YeKn)!M^8V?vfXa$qloXS7Olnh(w%poeNZb0^EDRLPt4^;E} zG8Zm+KUk||4W-Xs=KxKS`Y|m_Ml7A0XplVBM11T18DsQ_lt|^Avz*^qQ_GarXMcw$90@MKStO9lB07r$AQzv&lcmg%&AC=XD79Z%*+hY3xj|7l-@XMdl zh6VCx5?VDpr|8~S)-Lgln<0$`2-(8Eb?T{BFQ*Eep+@BoFS2-NDIeeAX{0crs3EsV zd|0#i1V*mk95THLmDh%ME5GKkS-*@_NH2Mt)p~e2;5T`EL%+&F5;_*UR!j<>Qv|62 zPc;@JW-TK%na+dxspI(zv+kkTF4e8fGV>&Zc+9-j3OS6{GhC?rfN63O6+2*K<9%T) zQRFuVn<{b1YpdyybzxVZg`Ja{T@cmapl404wziYz__w?~#&ZmVp38hGxHHVkU)Kvl z%;EM2XEqf{6)fV&8?;e2`5O{+o1Cvd3#nAxsc?F&9Cn{%LDJoyzWYB-H;`|`W;You zN(S*TLIWzPDI>;|ykuOQL6SO@7;d z19ZwSz>8DW_ZcrFT&c!Bq@F?L9UXK(c=)!3WOu*%zCq^X$7oyQ2QJ5#1&N)CNnkxh z?XrPy0M5h_EXS1L!uzjSKq9rJ!{6ro5D?B0J?uMXUTbu_yiE7a@FMdl zbCnz;N>0xoeAvPk+6qpYQK-qdhOjCV3?=Y_0# z=!)if7%X=v2`kXwK^9e6TG+idcT)ylEAGxtZ06LL#xge(6$PiyIaGj;JTC%$Q>U2P zN%cLNQ*F_DAJ{u8e;e?pp96kYXwyk|&=U;!yNjvz=Q5{o#N<2Y_~fPmS_E&H>yuc) zwPtt{lHI>k1&tZ423gaBXot>McEk;(OsOk#f<=sv0fv^@rgzg@^e^6eePHw2&r!VnThtFfTx*9Dd9QMjw9nrs zrgC(g?P90-W#TPg4l+r(^6|z%k0~Sp%8IV>-a%80Z-12RJ*uPas6WT)pBKYBr`)vO z=%e>Om5!&H-g#y&vuTu;n7!5?ij1U!D%u4E=xX7xB6{Lek))T> zl2h5g#T3J~T?bzy__DzQYCYy zEhyQj=Hmekr9E!mQum#7TaryiI^Qy84{lO)=pDW`x0u%3n(USyxKBysTGTmxC8o@8 zpHq+ppsQ=T(!l87PA(9Wjy*bPa9CTrZ4d7-Z!EOt70zN+`|h0WbU31YOODL2!7Zye zEyNN&5@#s#>Z!e!)<#po0c|cT9QEzS-)7qO*<$1KJFH&*poQs%iQDcy@AWJT6-y}T~p{QD$8j^{9U_Vh*fGz2>pR%FZ!|d4rnh;qls>kAu6d)c5Hx z`18)RYfhSn<#kJk2H#?4T}D-qhk%Np#>1Ksw4Kx4dO{VKUoA`~2FUO_^#kt~8e%hO zo=j$-Fr=H~zxBjqB6c;}D%Z)fch0>d%DX3i!1JJkv*3_0m?o4HEp z*yw&>7EQf0K@#R^%~!kdj7K(s@!2# zsygcCA2{}`&^D%Lsj8<2E;)~#ptnWaVrQztB=`CPWMD+3c71LY7HPP0Da(lFS#>8~ zgi-vKwL$LR2Y;Fs`S9;oLuLN_lLrEtGbgnuFPNX+KUKdxk469Z{`P&BkvwKj`HP*F zwkM-rwaUV*SL8o4%L108cgn{NwLHCV&{;fQJNGQ| zE639tqZgS$!ri*U-9gy7bfT=rLGM?P7z89HS~)B!WB3&p7ztaiQpd0!E8b3W1g1VN zB=18KI=O>|&N(Xza-P?x7`HH|`@)%rq>JpATZiR!L$-TeU3LeM{=diPCXg@RH*ec6 z=}j=7OF#Iw04*?NInNKIcCp-p72WRn7rnYp#9|at4K*3y=*1VBwkOdYUSw;V&2`Y! z-euv;llI%O1Kle6Ihxv8N81=_D|Q``1to6EJ>MG`ouO%j8ofE4HnMxmphZWn8*`<* zjn%RKzvq1M5v<~F)9mG@Fp+kxFsu@BGz-No`K+8J$M^Cr-pPAfO|-b@RB){m-bUgh z&oHTiW5s#2OFU#)YA*(`T*7t50;DuI3vHWmYFAL-k!7gDqp}?OtBSd`vA1VpQ)|9P z-=g>RrInenHdO!nP+56?Xt&JUei32%;;r%2K&)|fYQMno?AeO$4#jgzp*9|l<}Kgu zBzCE)pj3(?Nfl$e0a`XtlHFrJUq2E|YqlTkmmJ=6-PmaJ$tL^$iOU)Vg9BA_CJX=2 zG90WYzIDvb613vyl7gyTMp+r5RMEYEKM$554oS@fkNUnVc?X1xY(|2Ty`12CTj$zu z)qnd+z}OGHrz{CO%$X%jrW6{9VjDYhO(3PAJ3Szw^vrRj1ObK7~R&ru4MCt9D973sqeiqAJ%KfGvmecrYmEqJU~qR z({bkMB-Q0{Gg4r1;9>vHB2eDtswWc9jKnH7Vg)tr1X_v$ml@&7OS8dNg1>%VY6UV> z!w=v3s;vzCnguUI7DG4R6q2?=e>-Yz-JKn+o9#}K?PBKkfF9td(ahdNJD*#$+?BKS zb%~M%-%hwPyk+X{EsNFdx%rGa=aH-3{bq@fQoLe0S4(1idfyc||NPcSbpYS}==QmX zW@~IuiVwc+5d$?!>ERi(w&NrDT^Ro3`OYo#&e={=GfRsW{_-7ri1j=x)A=L)ak|%; ztMguwXy|BecEa8se7Hwr#G?%>UDvxJx$JJP(lo(@a-!bANjH@*pX*Xva9{i@-rDiB zb2dW&J?{Lgy&;Vn0IK@+TMZZL0t|UuiuN^gig(_cqI7?2`NopD-AsfRRwm}e=l9wu zr-d%&R_Xj$fk8#7jahpO(Dc`4G;XK4J7&X@f)!~ubG)6pWm+6LDKACGtSDkKwgMwV z-fF{&zY2P_B00gHwbuyWF8lEzwx#&?3fxbap8B3sa=dd$Vx@rUGCPoLGFRrE0Alw&(PB z<~d&`Cnxo|d~oc80LlFPQ&LI>Vd<1=?84JHbOE_D3LsFeZf#_5ya==GTNfi#^}~yV zAW_EfOzW(qWaljh%Amlnqc9!Hs(;Kz{b#4Y?pSs39*l|B%qJ^n#pzy5YRqDCUje@4 zL7A9$6mPwCQKG1G#UKTs4F43VVN6>i_d))5TSgePx@iiPz%sQl|DT6WlUXar{%CU0M1O z=n}W^lIkMmyrM-{lv?+upq+4(lc}q0+v45mTxH$r-tQS(ohV~<@Z0)&7mXS znm8rTZOIJ(V4e!!F^kW2iWWtwq?P_RL0zM%gp#J?uGg8#!ZHS9l_{N+!P(2P)_GkV-Y1G?qvht@0OytYov z#F_26J0Pn;y-n{8u|2+A9zpNdR?)&0zUd9=a-Pe%QU_(+f1d=X2l$Yl%JK_Nnkq9i zU?kDL6&ur21M1(ky?wrr8A0zl_gtZXDJCo&Es;p-g$SBYTRx#n9JiG#t}*yO-Ud5I zbsGP4QK6Xi4w%Nn@V*4O8PqKB((FzMrhTz5SdxFJyIj5?Lc6^|dPq^TX^K8)&S9~3 zuF!0dF(Eo-Wl%SMZN3g>&=m6A!VA3@Z!II-!a3^gO5R|9Pn0eO|6RIJ#ONNZ-owiI z;#Ql=H!Z23tT1b0LnNNXi<>?I-X(S?3iB??1jRYpsjwgqtTLIBJh#&OEsUXp8?nlY zmp0i6MQGwJEB_^ppx`$ljyKBHROJrS@29wR9Ppaf#q}I&!CrGL4>7XvdRaTM;nL(- z<$QGN$4>fmbS&yteeAoaU)wj3^ODfQd^-8661KpZE;FRK(0shzw#=g@aZaZv=a}K- z-TP$0mj6(@Ew@!q$oS6`R`0v{LfKqNGFe5IENxdApth}4qalDk$BPPkD)&yMVEJ}? z0b!=7*gDS?CGKP}-a+DLzH3@eHFQ$}e*4$QX9HA1^llhoJe-xwHu?k*viB*?)qT|X z7_wSqFewCB?*4T8JyF;bz1&CCRF{8~{|S4HF?DzN>G9C2q($=CSSij~01um5LZ&fj z2a}+Mrd}BXDp=0H4=WWheWBe${>G7>e^}5(m%Okwju|n7y&|9KAcuC&i8KjQBOdu| ziZ!I%KepM(jkG-A@h94tcFkTzF}~dJ-!}ovvv|yNS$QKmz?MJv{>@Cg zmX8%8o@M)P>LOg=cYk?IHNjo()=^1)_pI2x-**e5@=99PpjJR7doM@hYZ6e&2R%O) zRwhPlKw4yHS%PdSol+NmmKj#W2i<*ru-<}b4~bBO3JrJOJ{9Sgy+S`k2`p>fszCNVO?39 zR})U1hqPz-wlmKctbO;;`!NI0Q|NS)@M48xOKH>O>+ysl#R8AA;pO>0R* z;!gkSXyH3h$So-)Ra#xC70@A&nnsi#m+4u_7Lb~5`#U@^Mi`! zFOQxUtXTPzw=`QvqfX0NG>JNjFDrjR6y;!^#P@w1o|DKi$gqdHGL19emsiW>ugjY zVw2xP+pw)X0Y&5l~D zNSIYcnm=FAmG?%cJIa~9=X{o!R^|PxR=GR4;h-qIr#ZQ6>-7mLLxQCe3&hvIe)T_A zcU8N`t!6<4^ECFe0|Q-^0!1x&G-?N zf4JCHi!N{sn-3fqAu9?yF56*Gzmr9!}Z$&AZ2|HtRv1uxPAtS=!K!zB5ZC#IZMa#FAj z*TBW3=qi`}A4Jc+i;U&P#42Pm zlYc-enG;!z7}H!BOMC0r;sBsZO+R_2G<~Oj<(2P#&tmYaGNEs!{0jbC7r zLg7${9(TSA{$h`k-)1@QXBZvb7R8zD?!>CAp3mCbvJCygHvc>)xOPqs%N2Y+CoP*L zJ{m(lnZa@NV$4UO^QMnB-*QP9<9g#1DReS=lLEiMrEMx#s&<8Nd=)$M=aHlQf382f z<*bxc#Yva(z}JIcN%yg0wtI&rgmUD|bV->`R|$MP1}RW>}HFdg+AYj5h8>Bb1u zGnX2-rmyrBIcGf6xH-1}EG`5tZ2$E+e$VNf0Md{USV*awQ~i_|S2c$zuxq->JSfe# z)SNBNEK17h+TZ@Ar~Apma@T8_H^*pSgLgl*h>1oSM7tG>Mp!tDdu1oh{rXm)832CY zzvxWn-S6~viyyc1dMI$QGO-@Bpwwm%o;UUOU}t^6^Q;3V&DYJyNnX~;)na^g9@0y1 zKijX-V7-cc2%>(IGU}_L`{8XuM}6>;WbLR@deR(*oPX$`=oIfm6_s1J7_{ZX1;Rh4&p&r)>L zXJ33>eemeR4J6BJOG6@j?t<#4XU(+2Fh}{S8bj1=yp`i0UieYRsFOke$V`yuUg%0# z6YokE`(XRoQHH^HrrC^{LD|C#7RTfyZycYb4Km*2z02gD`R!|3ini722^wo7(-{{C zviaQ=*+pn{{f(oSoR&tGzu$hi#(ViVfh>KjXOWXCZFVjxOR-Vb*0N=aGNCoWTlpSd z_bB`{*R|g%hZA5aoEt5jvoH3m8w`?Ttijx(Jzd(Q&E-%xC!gJ~OO6c3tVc@zN|!&j ztVI{3ePk3!o-nh3iCzCPxNJ-h!*gOws;a&A1qIH92+fRhj?req%?7Oh7SYKu3`4a> zjX9D!XpU=Fipg3jS6b+kl*JomPh^UYIein0wz1ysyV7n_6-#YGK8b3nM{X}q0ngCUtf;!`NCXKpA_Ym$%%{j_%Nyj>PY7eGhe=u5y~O7;8#Zx z@?PvbVkIO@lJ+Jjy!B`N6cq%x-K>L=X-crI8*1Lks{lECo6Ml=uBq{l_?#ehgT6v;~$H4{h;SI z-06Kq5FpRhe^4PJj;C&|9e0DgQ=N4kXZ8gzCCt}-j)Beo#t`f*=={XRhxEDXLh68 z?y#yDx5&;cF}BsSqY^?A zN+LwqN9Tt&{SWrFo}a$oUp}Ab^ZtCE&-Fat=ZkYl+59tRMb|Y-;XT2|y9D#UkCxb) zCU>tyndoYJDb)nt`(`8~5y^KMA(tD@9vc)W{*&ml%ntD19EZJmv_G}#*2;`@I7y@G z5eL=z?KO#fUJ%^%5$UopF`LIuo#-=O6#*kx4ALh9L9cqhTO@Gc(S`C(mLHuazj@z4qYvBeOlMB`!e%Hil*Yk>k zUz-@gPlS#pr3bkX%f0u)w<(X*h~>WtudzbxTy)yySQi#V`(b<0`Mmg2t3gqthvRWx za}C9X-%Omaq(wf9!NhFzMT0+*y&Jc(JHg^e|C&8oYlR$z2VQOqfAxgpno~R8{3u^6 zLY{RvQBa<3{4hERIXaR2;+ApcSw9G>bIr4k{(WAlzNk2fm)J7kvp6I=#guTM1&V08 z`Es8qT%+AybnYD+a3tidmAMv8B2uhj&!uRZd7vROS`k2%Pefa=<8BU7Cs~=9IYG}A z-Cot~W^{&F&tL6f?Y_A&$@#>VLI|5O(iHi)2EWzFzGB^o8IL%ZvukMXV;y$YU*w`% zqnobOJp4sh4zw$JEfm=CoXUI%2F3FScxYx{tazTe z`>M0}l&|ez2r?p@84hT8c{-Bn;Ku<4CnKA!rJf@FsHVt=#ZX2R<4UM!xxlJS;HV>H zCWh$iq<+k7&p9$6q;2{VKE* z82Ej{&eB@`a;F8WDU{ZJGiXQHfP&aej|-h&8+CK2-X( z>E{7e_2_V9>rV-+)p@K)_8A4r2C>=i%X@Nz^^LK3#}ur5bl7=cV~y2H*=@%*AN0i4 z9yg|MyOp`|$9f`O@q-n@*MdIx1pm~~uK;&9#e}8xn0y3CcIhZ}(bpmcWrq#r0HK;_;caD!Vrn;tM*LRR4myrP| z3Y8KRpSC(mQwnCk#2dsn&zhFXi9g1s2GOLkbug0SGqDR6U4p3GlSI`w%$K?=CT#6F zscA&+y+1DT+Mi_FQ^$LmWJY8JrqMC`kkBst7kE6_kbb|UUF0~F^kPv z`_`jtjUc<|IinAuJ9i1yljM#V32oYk?z*YQS%>dM=ug#EdKK`gac72lDX|9gMR)D3 zV1omcNz0rkJ6BP&eJ*e+et|nngDdq=CalNA&h9G9mnCK9_G7K4X6gSH8qa!uOGv~e zgvn?t-4}I^YDgt%HilFtu{5G=Z?6&`Hku?`x&58pl;5xBJ%IrEE$JG$L!tQoR<9!l z+DzmxP5TSi<+-hr0{<1$IAskj}-8Yz5#;wSm#8PlIM8P+l+pjXFN)BNw zE`2QzK#NVxx#d%w7{f|8`vhsnTrpPszMZOA>Vwdcb~#J_GZb-m@GSV3ArRJoMgx2~ z!pw=((QAq3-)<4L(M?oMifyXTyy2TK_o{FG)RXes=bW!Q__x-2XA=$cH@1eKWs4D= z3(mcay+-ap>;lkLb=N@ue+me(?j_z=$KK= zi{9b$u;V03>?~8TKu%Vl%p&*&OU)>n>La;Lhj!6!(3P|svP)Yf-dOzas*W7Zm*}6W zT$^N0u%!3hFQ6E)!2G8_H2?9#GV;lQRBa}16Uj?q+qop6tP<#|r4JR>9O=j2u6N`Z zeYY}(saZLj6B9-bZ}Hc5VE^gF`%|9_0!DlD#9?`J6sbp();j+$6z&psdFN?F`l~O1 zUBUtJArheww!T86M}(oG#Di+-<^~iY=pMWq9nV3w-f@9U&>h6h>J?}6_%m9yb%68CYeA-5*tY%RbGBVT zUB$jSHyyje4Pt*3!k52fdD&WQKPvd>(q7pI>gSE`t1ZOBygjjVY;esP^^DKvJjrFP zO$+DKLy<_2sInSj~ z)9TUIB>vw+(($jZve9!MrqhV^73~EbgMFxk6dgO1<`C0_!;vbR^(Yqr{@|Dtx997z z^I1^I*ghDByOGYiNjvQi+T~Oly`v(eqFwG#*?)4+2Ii-on}-!^)og>Z=6liYXKMAW zNqPmP_;?CN)g|Ap#aFzTr9J znOn1bMU|7gzZ$jQSL5910w`{e0++8DGFoU5OA{I|UvQeXwP@J+N8Psn9+Hi|6}7)y z;8QP~PFMP!Dk!J7VvGT^VqLullg)`(Y2c_-Y4=bCcsn06MjWqx>U#yb@wD05DbC1U zN=`7#g0*yA6Ge{zSCw3F*v*hEE#rq6;W~sml-fvn&e+LzrHFdAUFP)5Fq1c$;+V@+PE~3G7C&xKy(_BemZxg zGApouwMqnh_m70e^JZeU^k?V=mZkxBik#>w?pQsFRocx`k)?s4;C)uZ-&FC#a2Wpy zIgt|r|1~&VTGYex8oJ)JF)ZF~jaso&R0X=T@2Bpl>A3ppq7U2QU76%8!#7Wv)^)-w zdumVxnC!cuLjt!imv9Doym86z7Bg+MBCh{qlid^&EV0N z{W7wT5QabT*n#C_8ogNLYlqA$)+=+v;Dc7kE+WFc3De zsGOIZ5~zy>m+ihc{+0(6F%fVW-=a3;0vL^P&uhqZJRdL+bbsAa3vpWxe^Gm2HZmsn$O8aF0XHXo7dw{?J0tW~jAaH=d0RjgI93b$& bj==Fli0^ne^7x#$CqXYbfeTbW5BTQ4%0q{q literal 0 HcmV?d00001 diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1e2217417eaddbe44e3c0288cb2453b385cb4c9e GIT binary patch literal 59058 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 + Omnisolver +

-## Installation +--- -Currently, the `omnisolver-bruteforce` package requires working CUDA installation. +

-To install the plugin first set the `CUDAHOME` environmental library to your CUDA instalaltion location, e.g.: +

+ + Tests + + +Docs + +

-```shell -# Rmember, your actual location may vary! -export CUDAHOME=/usr/local/cuda -``` +**Documentation:** https://euro-hpc-pl.github.io/omnisolver -and then run: +**Source code:** https://github.com/euro-hpc-pl/omnisolver -```shell -pip install omnisolver-bruteforce -``` +--- -> **Warning** -> If you don't set the `CUDAHOME` directory, an attempt will be made to deduce it based on the location of your `nvcc` compiler. -> However, this process might not work in all the cases and should not be relied on. +Omnisolver is a collection of Binary Quadratic Model solvers and a framework for implementing them. -## Command line usage -```text -usage: omnisolver bruteforce-gpu [-h] [--output OUTPUT] [--vartype {SPIN,BINARY}] [--num_states NUM_STATES] [--suffix_size SUFFIX_SIZE] [--grid_size GRID_SIZE] - [--block_size BLOCK_SIZE] [--dtype {float,double}] - input -Bruteforce (a.k.a exhaustive search) sampler using CUA-enabled GPU -positional arguments: - input Path of the input BQM file in COO format. If not specified, stdin is used. +## Why Omnisolver? + +### Benefits for the end-users + +Omnisolver contains a selection of standard and more sophisticated algorithms for solving BQMs. All solvers are available through intuitive CLI or from Python scripts as dimod based Samplers. + +### Benefits for solver creators + +Omnisolver allows developer to focus on algorithms instead of common tasks like handling input/output or creating CLI. + +## Quickstart -optional arguments: - -h, --help show this help message and exit - --output OUTPUT Path of the output file. If not specified, stdout is used. - --vartype {SPIN,BINARY} - Variable type - --num_states NUM_STATES + + +``` +# Install base omnisolver package +$ pip install omnisolver +---> 100% +Successfuly installed omnisolver +# Install chosen plugins (e.g. parallel-tempering solver) +$ pip install omnisolver-pt +---> 100% +Successfuly installed omnisolver-pt +# Create an instance file in COOrdinate format +$ echo "0 1 1.0 +> 1 2 1.0 +> 2 0 1.0" > instance.txt +# Run solver +$ omnisolver pt --vartype SPIN instance.txt +0,1,2,energy,num_occurrences +1,-1,-1,-1.0,1 ``` -## Documentation -```{toctree} -:maxdepth: 1 +## What's next? + +Here are some resources to get you started: + +- Start with user guide to learn about the installation methods and general usage patterns. +- Discover available solvers in our plugin list. +- If you are interested in developing your own solver, or are interested in in-depth details of how the Omnisolver + works, check our solver creator guide and reference manual. + +## Citing + +If you used Omnisolver in your research, consider citing it in your paper. +You can use the following BibTeX entry: + +```text +@article{omnisolver2023, + title = {Omnisolver: An extensible interface to Ising spin–glass and QUBO solvers}, + journal = {SoftwareX}, + volume = {24}, + pages = {101559}, + year = {2023}, + doi = {https://doi.org/10.1016/j.softx.2023.101559}, + url = {https://www.softxjournal.com/article/S2352-7110(23)00255-8/}, + author = {Konrad Jałowiecki and {\L}ukasz Pawela}, +} ``` diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js new file mode 100644 index 0000000..396f03b --- /dev/null +++ b/docs/javascripts/mathjax.js @@ -0,0 +1,18 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } +}; + +document$.subscribe(() => { + + + MathJax.typesetPromise() +}) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 8084272..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/reference/omnisolver_bruteforce_distributed.md b/docs/reference/omnisolver_bruteforce_distributed.md new file mode 100644 index 0000000..8860640 --- /dev/null +++ b/docs/reference/omnisolver_bruteforce_distributed.md @@ -0,0 +1,2 @@ +# ::: omnisolver.bruteforce.gpu.distributed + handler: python diff --git a/docs/reference/omnisolver_bruteforce_sampler.md b/docs/reference/omnisolver_bruteforce_sampler.md new file mode 100644 index 0000000..84cc6a5 --- /dev/null +++ b/docs/reference/omnisolver_bruteforce_sampler.md @@ -0,0 +1,2 @@ +# ::: omnisolver.bruteforce.gpu.sampler + handler: python diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 09f95f3..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -sphinx~=6.1.3 -sphinx-autoapi~=2.0.1 -pydata-sphinx-theme~=0.13.1 -sphinx-term~=0.1 -myst-parser~=1.0.0 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..2d13988 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,66 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json +site_name: Omnisolver-Bruteforce +site_author: "Konrad Jałowiecki & Łukasz Pawela" +copyright: "" +theme: + name: material + logo: assets/logo.png + palette: + primary: custom + features: + - navigation.tabs + - navigation.tracking +extra: + version: + provider: mike +extra_css: + - stylesheets/extra.css +nav: + - User guide: userguide.md + #- Solver creator guide: creatorguide.md + - Reference manual: + - omnisolver.bruteforce.gpu.sampler: reference/omnisolver_bruteforce_sampler.md + - omnisolver.bruteforce.gpu.distributed: reference/omnisolver_bruteforce_distributed.md + - Plugin list: plugins.md +plugins: + - search + - mike: + canonical_version: latest + version_selector: true + - termynal: + prompt_literal_start: + - "$" + - mkdocstrings: + handlers: + python: + options: + annotation_path: brief + heading_level: 2 + show_source: false + show_root_toc_entry: false + show_signature_annotations: true + members_order: source + separate_signature: true + docstring_style: sphinx + show_if_no_docstring: true + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + show_docstring_returns: true + + - with-pdf: + render_js: true + cover_subtitle: "Exhaustive search plugin for Omnisolver" +repo_name: Omnisolver +repo_url: https://github.com/euro-hpc-pl/omnisolver +markdown_extensions: + - pymdownx.superfences + - pymdownx.arithmatex: + generic: true + - admonition + - pymdownx.tabbed: + alternate_style: true +extra_javascript: + - javascripts/mathjax.js + - https://polyfill.io/v3/polyfill.min.js?features=es6 + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js From 026744f1a7b8dff371378126b3968608c1643f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:54:04 +0100 Subject: [PATCH 15/22] Add Cython wrapper to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fb9f65d..2902970 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,5 @@ dmypy.json # End of https://www.toptal.com/developers/gitignore/api/python instances/ + +bruteforce_wrapper_gpu.cpp \ No newline at end of file From a67a2dfc9ee1b6694ad9090e498d6208378b107c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:54:21 +0100 Subject: [PATCH 16/22] Relax dependencies to make package installable on newer Python versions --- pyproject.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 189d17f..caaa913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,14 @@ build-backend = "setuptools.build_meta" [project] readme = "README.md" -requires-python = ">=3.7,<3.10" +requires-python = ">=3.7" name = "omnisolver-bruteforce" description = "Bruteforce (a.k.a. exhaustive search) Plugin for Omnisolver" dependencies = [ - "omnisolver ~= 0.0.3", + "omnisolver >= 0.0.3", "dimod ~= 0.12", - "numba ~= 0.56.4", - "pluggy ~= 0.13.1", - "numpy ~= 1.20.3" + "numba ~= 0.58", + "numpy ~= 1.26" ] dynamic = ["version"] From c19467d3cd76a3085c1f76a6547dc41d2f01bd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:54:47 +0100 Subject: [PATCH 17/22] Use new Omnisolver plugin system --- omnisolver/bruteforce/gpu/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/omnisolver/bruteforce/gpu/__init__.py b/omnisolver/bruteforce/gpu/__init__.py index d0c35ef..5b868f2 100644 --- a/omnisolver/bruteforce/gpu/__init__.py +++ b/omnisolver/bruteforce/gpu/__init__.py @@ -1,12 +1,11 @@ """Implementation of parallel tempering plugin for Omnisolver.""" -from omnisolver.common.plugin import Plugin, plugin_from_specification, plugin_impl +from omnisolver.common.plugin import Plugin, plugin_from_specification from pkg_resources import resource_stream from yaml import safe_load from .sampler import BruteforceGPUSampler -@plugin_impl def get_plugin() -> Plugin: """Get package name and resource path.""" specification = safe_load(resource_stream("omnisolver.bruteforce.gpu", "gpu.yml")) From a00745c40b99906afa45bc83651722393a99a7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:55:15 +0100 Subject: [PATCH 18/22] Correctly restore variables labelling --- omnisolver/bruteforce/gpu/distributed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/omnisolver/bruteforce/gpu/distributed.py b/omnisolver/bruteforce/gpu/distributed.py index 1d50a29..9729a0c 100644 --- a/omnisolver/bruteforce/gpu/distributed.py +++ b/omnisolver/bruteforce/gpu/distributed.py @@ -91,7 +91,7 @@ def sample( solve_time_in_seconds = perf_counter() - start_counter result = concatenate(subsolutions).truncate(num_states) result.info["solve_time_in_seconds"] = solve_time_in_seconds - return result + return result.relabel_variables(mapping) @property def parameters(self) -> typing.Dict[str, typing.Any]: From fd9cf6fb81ed19d24f2115b9e2bdab863c219fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:55:49 +0100 Subject: [PATCH 19/22] Improve parameter naming --- omnisolver/bruteforce/gpu/sampler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/omnisolver/bruteforce/gpu/sampler.py b/omnisolver/bruteforce/gpu/sampler.py index d32c554..81a960c 100644 --- a/omnisolver/bruteforce/gpu/sampler.py +++ b/omnisolver/bruteforce/gpu/sampler.py @@ -24,7 +24,7 @@ def sample( grid_size, block_size, num_steps_per_kernel=16, - partial_diff_buff_depth=1, + partial_diff_buffer_depth=1, dtype=np.float32, ): """Solve Binary Quadratic Model using exhaustive (bruteforce) search on the GPU. @@ -49,7 +49,7 @@ def sample( grid_size, block_size, num_steps_per_kernel, - partial_diff_buff_depth, + partial_diff_buffer_depth, dtype, ).change_vartype("SPIN", inplace=False) @@ -78,7 +78,7 @@ def sample( block_size, suffix_size, num_steps_per_kernel, - partial_diff_buff_depth, + partial_diff_buffer_depth, ) else: gpu_search( From 671b2e788cc778517a74ff847f505d1e91d403c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:56:09 +0100 Subject: [PATCH 20/22] Format extension and improve style --- omnisolver/extensions/bruteforce_gpu.cu | 262 +++++++++++++----------- 1 file changed, 137 insertions(+), 125 deletions(-) diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index 87577ec..27d16ba 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -1,31 +1,38 @@ // Implementation of exhaustive search using a CUDA--enabled GPU. +#include "vectors.h" + #include #include -#include #include +#include #include #include -#include "vectors.h" // Basic error checking (used only for kernel launches) // It should throw an instance of std::runtime_error containing the decoded // cuda runtime error message and also filename and line at which the error // occurred. -#define cuda_error_check(code) { cuda_assert((code), __FILE__, __LINE__); } +#define cuda_error_check(code) \ + { \ + cuda_assert((code), __FILE__, __LINE__); \ + } -inline void cuda_assert(cudaError_t code, const char *file, int line) +__device__ __forceinline__ int thread_id() { return blockIdx.x * blockDim.x + threadIdx.x; } + +__device__ __forceinline__ int grid_size() { return blockDim.x * gridDim.x; } + +inline void cuda_assert(cudaError_t code, const char* file, int line) { - if (code != cudaSuccess) - { - std::stringstream what; - what << "File: " << __FILE__ << " at " << __LINE__ << ", error: " << cudaGetErrorString(code); - throw std::runtime_error(what.str()); - } + if (code != cudaSuccess) { + std::stringstream what; + what << "File: " << __FILE__ << " at " << __LINE__ + << ", error: " << cudaGetErrorString(code); + throw std::runtime_error(what.str()); + } } // Compute i-th Gray code. -__host__ __device__ -uint64_t gray(uint64_t index) { return index ^ (index >> 1); } +__host__ __device__ uint64_t gray(uint64_t index) { return index ^ (index >> 1); } // Find First bit Set in a number. // Following common convention for this function it returns bits indexed from 1 @@ -34,12 +41,13 @@ uint64_t gray(uint64_t index) { return index ^ (index >> 1); } // ffs(7) = 1 // ffs(10) = 2 // ffs(24) = 4 -int ffs(uint64_t number) { - if(number == 0) { +int ffs(uint64_t number) +{ + if (number == 0) { return 0; } int result = 1; - while((number & 1) != 1) { + while ((number & 1) != 1) { number >>= 1; result++; } @@ -47,30 +55,31 @@ int ffs(uint64_t number) { return result; } -// Given a QUBO and a state, compute how the energy changes if the i-th bit of state -// is flipped. -// Specifically, E(.) is energy function of the qubo, then this function computes -// E(s) - E(s'), where s' is the state after flipping i-th bit of s. -// One can easily verify that to compute the difference above we need only the -// i-th row of QUBO, and hence we already pass this row instead of computign the offset. +// Given a QUBO and a state, compute how the energy changes if the i-th bit of +// state is flipped. Specifically, E(.) is energy function of the qubo, then +// this function computes E(s) - E(s'), where s' is the state after flipping +// i-th bit of s. One can easily verify that to compute the difference above we +// need only the i-th row of QUBO, and hence we already pass this row instead +// of computign the offset. template -__host__ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) { +__host__ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t state, int i) +{ int qi = (state >> i) & 1; // This is the i-th bit of state - T total = qubo_row[i]; // Start accumulating from the linear term + T total = qubo_row[i]; // Start accumulating from the linear term uint32_t word = state & 0xFFFFFFFF; // Go through each bit of state - for(int j = 0; j < 32 && j < N; j++) { - if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + for (int j = 0; j < 32 && j < N; j++) { + if (i != j && (word % 2)) { // except the one to flip, it's already accounted for total += qubo_row[j]; } word /= 2; //>>= 1; // move to next bit } word = state >> 32; - for(int j=32; j < N; j++) { - if(i != j && (word % 2)) { // except the one to flip, it's already accounted for + for (int j = 32; j < N; j++) { + if (i != j && (word % 2)) { // except the one to flip, it's already accounted for total += qubo_row[j]; } word /= 2; // move to next bit @@ -82,21 +91,25 @@ __host__ __device__ __forceinline__ T energy_diff(T* qubo_row, int N, uint64_t s } template -__device__ __forceinline__ T energy_diff_reduced(T* qubo_row, int N, int offset, uint64_t state, int i, T common_part) { +__device__ __forceinline__ T +energy_diff_reduced(T* qubo_row, int N, int offset, uint64_t state, int i, T common_part) +{ int qi = (state >> i) & 1; // This is the i-th bit of state - T total = common_part; // Start accumulating from the linear term + T total = common_part; // Start accumulating from the linear term uint32_t word = (state >> offset) & 0xFFFFFFFF; // Go through each bit of state - for(int j = offset; j < 32 + offset && j < N; j++) { - if(word % 2) total += qubo_row[j]; - word /= 2; //>>= 1; // move to next bit + for (int j = offset; j < 32 + offset && j < N; j++) { + if (word % 2) + total += qubo_row[j]; + word /= 2; // move to next bit } - word = state >> (32+offset); - for(int j=32+offset; j < N; j++) { - if(word % 2) total += qubo_row[j]; + word = state >> (32 + offset); + for (int j = 32 + offset; j < N; j++) { + if (word % 2) + total += qubo_row[j]; word /= 2; // move to next bit } @@ -106,14 +119,14 @@ __device__ __forceinline__ T energy_diff_reduced(T* qubo_row, int N, int offset, } // Initialize first states and energies for further processing. -// The idea is as follows. Each state (which is N-bit number) is (logically) split into -// two parts: -// l-bit part specific to state -// k-bit part that changes in all states in the same way for all states in given iteration. -// This function computes energy of a state with some l-bits set according to index, +// The idea is as follows. Each state (which is N-bit number) is (logically) +// split into two parts: l-bit part specific to state k-bit part that changes +// in all states in the same way for all states in given iteration. This +// function computes energy of a state with some l-bits set according to index, // and k least significant bits set to 0. template -__device__ void _init(T* qubo, int N, uint64_t* states, T* energies, uint64_t index) { +__device__ void _init(T* qubo, int N, uint64_t* states, T* energies, uint64_t index) +{ T energy = 0.0; uint64_t state = 0, suffix = index, offset = 0; @@ -121,7 +134,7 @@ __device__ void _init(T* qubo, int N, uint64_t* states, T* energies, uint64_t in // Instead we enumerate bits from the most significant one. // For instance, if index=12, which is 110 in binary, we will set the three // most significant bits of state to 011. - while(suffix > 0) { + while (suffix > 0) { // Find first set (note: not our function, this one is CUDA's intrinsic) uint64_t bit_in_suffix = __ffsll(suffix); // Compute bit index from the most significant position @@ -143,36 +156,28 @@ __device__ void _init(T* qubo, int N, uint64_t* states, T* energies, uint64_t in states[index] = state; } -// Kernel that basically launched the _init function above for all states and energies +// Kernel that basically launches the _init function above for all states and +// energies template -__global__ void init( - T* qubo, - int N, - T* energies, - uint64_t* states, - uint64_t states_in_chunk -) { - for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { +__global__ void init(T* qubo, int N, T* energies, uint64_t* states, uint64_t states_in_chunk) +{ + for (uint64_t i = thread_id(); i < states_in_chunk; i += grid_size()) { _init(qubo, N, states, energies, i); } } // A single step of bruteforce algorithm. -// Recall that we split all states (logically) into l-most significant fixed bits -// and k-least significant bits that are modified, one at a time, in each iteration. -// This kernel flips one of those k-significant bits. +// Recall that we split all states (logically) into l-most significant fixed +// bits and k-least significant bits that are modified, one at a time, in each +// iteration. This kernel flips one of those k-significant bits. template __global__ void single_step( - T* qubo, - int N, - T* energies, - uint64_t* states, - int bit_to_flip, - uint64_t states_in_chunk -) { + T* qubo, int N, T* energies, uint64_t* states, int bit_to_flip, uint64_t states_in_chunk +) +{ T* qubo_row = qubo + N * bit_to_flip; - for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < states_in_chunk; i += blockDim.x * gridDim.x) { + for (uint64_t i = thread_id(); i < states_in_chunk; i += grid_size()) { uint64_t state = states[i]; T energy = energies[i]; energies[i] = energy - energy_diff(qubo_row, N, state, bit_to_flip); @@ -180,14 +185,14 @@ __global__ void single_step( } } - template -__device__ __forceinline__ T* row_ptr(T* array_2d, uint64_t n_cols, uint64_t row_id) { +__device__ __forceinline__ T* row_ptr(T* array_2d, uint64_t n_cols, uint64_t row_id) +{ return array_2d + n_cols * row_id; } - -// Kernel performing whole ground state (and only ground state) computation in a single step +// Kernel performing whole ground state (and only ground state) computation in +// a single step template __global__ void find_ground( T* qubo, @@ -202,31 +207,32 @@ __global__ void find_ground( T* prefix_diff_buffer, T* partial_diff_buffer, int partial_diff_buffer_depth -) { +) +{ uint64_t chunk_size = 1L << suffix_size; T tmp_energy, candidate_energy; uint64_t tmp_state, candidate_state; int bit_to_flip; int qi; - for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + for (uint64_t i = thread_id(); i < chunk_size; i += grid_size()) { candidate_energy = best_energies[i]; tmp_energy = energies[i]; candidate_state = best_states[i]; tmp_state = states[i]; - for(uint64_t j=0; j < num_iterations; j++) { + for (uint64_t j = 0; j < num_iterations; j++) { bit_to_flip = bit_buffer[j]; - if(bit_to_flip < partial_diff_buffer_depth) { + if (bit_to_flip < partial_diff_buffer_depth) { qi = (tmp_state >> bit_to_flip) & 1; - tmp_energy = tmp_energy - (2 * qi - 1) * ( - row_ptr(partial_diff_buffer, chunk_size, bit_to_flip)[i] + - prefix_diff_buffer[j] - ); + tmp_energy = tmp_energy + - (2 * qi - 1) + * (row_ptr(partial_diff_buffer, chunk_size, bit_to_flip)[i] + + prefix_diff_buffer[j]); } else { tmp_energy -= energy_diff_reduced( row_ptr(qubo, N, bit_to_flip), N, - N-suffix_size, + N - suffix_size, tmp_state, bit_to_flip, prefix_diff_buffer[j] @@ -234,7 +240,7 @@ __global__ void find_ground( } tmp_state = tmp_state ^ (1L << bit_to_flip); - if(tmp_energy < candidate_energy) { + if (tmp_energy < candidate_energy) { candidate_energy = tmp_energy; candidate_state = tmp_state; } @@ -246,7 +252,6 @@ __global__ void find_ground( } } - template __global__ void _initialize_partial_diff_buffer( T* qubo, @@ -255,20 +260,22 @@ __global__ void _initialize_partial_diff_buffer( uint64_t suffix_size, T* partial_diff_buffer, int partial_diff_buffer_depth -) { +) +{ uint64_t chunk_size = 1L << suffix_size; uint64_t shifted_state, tmp_state; T total; T* qubo_row; - for(uint64_t i=blockIdx.x * blockDim.x + threadIdx.x; i < chunk_size; i += blockDim.x * gridDim.x) { + for (uint64_t i = thread_id(); i < chunk_size; i += grid_size()) { shifted_state = states[i] >> (N - suffix_size); - for(int bit_to_flip=0; bit_to_flip < partial_diff_buffer_depth; bit_to_flip++) { + for (int bit_to_flip = 0; bit_to_flip < partial_diff_buffer_depth; bit_to_flip++) { total = 0; tmp_state = shifted_state; qubo_row = qubo + N * bit_to_flip; - for(int k=N-suffix_size; k < N; k++) { - if(tmp_state % 2) total += qubo_row[k]; + for (int k = N - suffix_size; k < N; k++) { + if (tmp_state % 2) + total += qubo_row[k]; tmp_state /= 2; } @@ -277,30 +284,32 @@ __global__ void _initialize_partial_diff_buffer( } } - // Predicate used in thrust::copy_if // Given a tuple (state, energy), copy this copule iff energy < threshold. -template -struct energy_less_then { - energy_less_then(T threshold) : threshold(threshold) {}; +template struct energy_less_then { + energy_less_then(T threshold) + : threshold(threshold) {}; + T threshold; - __host__ __device__ - inline bool operator () (const thrust::tuple& pair) { + __host__ __device__ inline bool operator()(const thrust::tuple& pair) + { return thrust::get<1>(pair) < threshold; } }; // The pièce de résistance of this module, the search function. -// QUBO: N x N matrix of floats or doubles (depending on the template parameter T) -// num_states: requested size of the low energy spectrum -// states_out, energies_out: output buffers, should be of size num_states -// grid_size, block_size: definition of grid on which init and single_step kernels will -// be launched. Warning: other kernels might be launched by thrust, and we cannot control -// their grid (and frankly, thrust's judgement is probably better then our judgement) -// suffix_size: the number l determining how many most significant bits in each state are -// fixed. Since there are 2 ** l possible configurations of l-bits, the temporary buffers -// for energies and states will also have 2 ** l items. +// QUBO: N x N matrix of floats or doubles (depending on the template parameter +// T) num_states: requested size of the low energy spectrum states_out, +// energies_out: output buffers, should be of size num_states grid_size, +// block_size: definition of grid on which init and single_step kernels will +// be launched. Warning: other kernels might be launched by thrust, and we +// cannot control their grid (and frankly, thrust's judgement is probably +// better then our judgement) +// suffix_size: the number l determining how many most significant bits in each +// state are +// fixed. Since there are 2 ** l possible configurations of l-bits, the +// temporary buffers for energies and states will also have 2 ** l items. template void search( T* qubo, @@ -311,7 +320,8 @@ void search( int grid_size, int block_size, int suffix_size -) { +) +{ // chunk_size = 2 ** suffix_size uint64_t chunk_size = 1 << suffix_size; // Device vectors with qubo @@ -341,15 +351,14 @@ void search( thrust::sort_by_key(energies.begin(), energies.end(), states.begin()); // The initial states and energies are becoming the first approximation of - // the low energy spectrum. We copy first num_states of them into best_spectrum_it. - thrust::copy( - current_spectrum_it, current_spectrum_it + num_states, best_spectrum_it - ); + // the low energy spectrum. We copy first num_states of them into + // best_spectrum_it. + thrust::copy(current_spectrum_it, current_spectrum_it + num_states, best_spectrum_it); // Iterate in chunks in gray code order. - for(uint64_t i = 1; i < num_chunks; i++) { - // To compute bit to flip compute two consecutive gray codes and see at which - // bit they differ. Subtract 1 because ffs counts from 1. + for (uint64_t i = 1; i < num_chunks; i++) { + // To compute bit to flip compute two consecutive gray codes and see at + // which bit they differ. Subtract 1 because ffs counts from 1. int bit_to_flip = ffs(gray(i) ^ gray(i - 1)) - 1; // Perform single step of energy computation and check if it succeeded. single_step<<>>( @@ -357,20 +366,20 @@ void search( ); cuda_error_check(cudaGetLastError()); - - // Find the maximum energy in current approximation if low enrgy spectrum. + // Find the maximum energy in current approximation if low enrgy + // spectrum. T threshold = *thrust::max_element( thrust::device, best_energies.begin(), best_energies.begin() + num_states ); - // From the currently computed chunk, the only candidates to enter the low energy - // spectrum are the ones that have energy < threshold. - // Hence, we copy only those states and energies into the range directly being + // From the currently computed chunk, the only candidates to enter the + // low energy spectrum are the ones that have energy < threshold. Hence, + // we copy only those states and energies into the range directly being // the current approximation of low energy spectrum. auto candidates_it = best_spectrum_it + num_states; - // We also store the position at which we placed the last candidate state. - // This way, we can sometimes sort much smaller range. + // We also store the position at which we placed the last candidate + // state. This way, we can sometimes sort much smaller range. auto end = thrust::copy_if( thrust::device, current_spectrum_it, @@ -379,7 +388,8 @@ void search( energy_less_then(threshold) ); - // This sort effectively combines the current approximation and candidates. + // This sort effectively combines the current approximation and + // candidates. thrust::sort_by_key( best_energies.begin(), best_energies.begin() + num_states + (end - candidates_it), @@ -426,7 +436,8 @@ void search_ground_only( int suffix_size, int num_steps_per_kernel, int partial_diff_buffer_depth -) { +) +{ uint64_t chunk_size = 1L << suffix_size; device_vector qubo_gpu(qubo, qubo + N * N); @@ -454,7 +465,9 @@ void search_ground_only( best_energies = energies; best_states = states; - _initialize_partial_diff_buffer<<>>((T*)qubo_gpu, N, states, suffix_size, (T*) partial_diff_buffer, partial_diff_buffer_depth); + _initialize_partial_diff_buffer<<>>( + (T*)qubo_gpu, N, states, suffix_size, (T*)partial_diff_buffer, partial_diff_buffer_depth + ); cuda_error_check(cudaGetLastError()); cudaDeviceSynchronize(); @@ -463,17 +476,17 @@ void search_ground_only( base_energy = 0; base_state = 0; - for(uint64_t i = 0; i < (1L << (N - suffix_size)) / num_steps_per_kernel; i++) { - for(uint64_t j = 0; j < num_steps_per_kernel; j++) { + for (uint64_t i = 0; i < (1L << (N - suffix_size)) / num_steps_per_kernel; i++) { + for (uint64_t j = 0; j < num_steps_per_kernel; j++) { counter += 1; next_gray_code = gray(counter); - int bit_to_flip = ffs(last_gray_code ^ next_gray_code)-1; + int bit_to_flip = ffs(last_gray_code ^ next_gray_code) - 1; src_bit_flip_buffer[j] = bit_to_flip; src_prefix_diff_buffer[j] = qubo[(N + 1) * bit_to_flip]; - for(int k = 0; k < N - suffix_size; k++) { - if(((base_state >> k) % 2) && k != bit_to_flip) { - src_prefix_diff_buffer[j] += qubo[N * bit_to_flip + k]; - } + for (int k = 0; k < N - suffix_size; k++) { + if (((base_state >> k) % 2) && k != bit_to_flip) { + src_prefix_diff_buffer[j] += qubo[N * bit_to_flip + k]; + } } last_gray_code = next_gray_code; @@ -492,12 +505,11 @@ void search_ground_only( best_states, suffix_size, num_steps_per_kernel, - (int*) bit_flip_buffer, - (T*) prefix_diff_buffer, + (int*)bit_flip_buffer, + (T*)prefix_diff_buffer, (T*)partial_diff_buffer, partial_diff_buffer_depth ); - } cuda_error_check(cudaGetLastError()); From 6e7eb73b802933e0fda4027acb6635c6d82e8f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 15 Jan 2024 00:56:37 +0100 Subject: [PATCH 21/22] Add .clang-format file --- .clang-format | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b33b3b2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: WebKit +AlignAfterOpenBracket: BlockIndent +ColumnLimit: 100 +BinPackParameters: false +BinPackArguments: false +IncludeBlocks: Regroup From e0660681dde244cf443c5f178a2e896e6ba97c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Ja=C5=82owiecki?= Date: Mon, 27 May 2024 01:15:53 +0200 Subject: [PATCH 22/22] Fix corner case when num_steps_per_kernel is larger than prefix size --- omnisolver/extensions/bruteforce_gpu.cu | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/omnisolver/extensions/bruteforce_gpu.cu b/omnisolver/extensions/bruteforce_gpu.cu index 27d16ba..1adc65d 100644 --- a/omnisolver/extensions/bruteforce_gpu.cu +++ b/omnisolver/extensions/bruteforce_gpu.cu @@ -1,8 +1,6 @@ // Implementation of exhaustive search using a CUDA--enabled GPU. #include "vectors.h" -#include -#include #include #include #include @@ -450,6 +448,9 @@ void search_ground_only( energy_vector prefix_diff_buffer(num_steps_per_kernel); energy_vector partial_diff_buffer(partial_diff_buffer_depth * chunk_size); + num_steps_per_kernel = std::min(long(num_steps_per_kernel), 1L << (N - suffix_size)); + + std::vector src_bit_flip_buffer(num_steps_per_kernel); std::vector src_prefix_diff_buffer(num_steps_per_kernel); @@ -476,7 +477,9 @@ void search_ground_only( base_energy = 0; base_state = 0; + for (uint64_t i = 0; i < (1L << (N - suffix_size)) / num_steps_per_kernel; i++) { + cudaDeviceSynchronize(); for (uint64_t j = 0; j < num_steps_per_kernel; j++) { counter += 1; next_gray_code = gray(counter); @@ -488,7 +491,6 @@ void search_ground_only( src_prefix_diff_buffer[j] += qubo[N * bit_to_flip + k]; } } - last_gray_code = next_gray_code; base_state ^= (1L << src_bit_flip_buffer[j]); }