Skip to content

Commit e29e7c0

Browse files
Add config option to leverage all cores for sabre (#12780) (#12841)
* Add config option to leverage all cores for sabre By default when running sabre in parallel we use a fixed number of threads (depending on optimization level). This was a tradeoff made for having deterministic results across multiple systems with a fixed seed set. However when running qiskit on systems with a lot of CPUs available we're leaving potential performance on the table by not using all the available cores. This new flag lets users opt-in to running sabre with n trials for n CPUs to potentially get better output results from the transpiler, with minimal to no runtime overhead, at the cost of the results not necessarily being reproducible when run on a different computer. * Apply suggestions from code review Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Rework logic to use the default if larger than CPU_COUNT This commit refactors the logic added in the previous commit to a single helper function. This reduces the code duplication and makes it easier to work with. While doing this the logic has been updated so that when the flag is set and the default number of trials is larger than the CPU_COUNT we use the default. This means the logic when the flag is set is to run `max(default_trials, CPU_COUNT)` which should better match user expectations around the flag. --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> (cherry picked from commit f8ac2ad) Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
1 parent 04b4d20 commit e29e7c0

File tree

3 files changed

+87
-18
lines changed

3 files changed

+87
-18
lines changed

qiskit/transpiler/preset_passmanagers/builtin_plugins.py

+52-18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
"""Built-in transpiler stage plugins for preset pass managers."""
1414

15+
import os
16+
1517
from qiskit.transpiler.passmanager import PassManager
1618
from qiskit.transpiler.exceptions import TranspilerError
1719
from qiskit.transpiler.passes import BasicSwap
@@ -63,6 +65,10 @@
6365
SXGate,
6466
SXdgGate,
6567
)
68+
from qiskit.utils.parallel import CPU_COUNT
69+
from qiskit import user_config
70+
71+
CONFIG = user_config.get_config()
6672

6773

6874
class DefaultInitPassManager(PassManagerStagePlugin):
@@ -397,11 +403,12 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
397403
pass_manager_config.initial_layout,
398404
)
399405
if optimization_level == 0:
406+
trial_count = _get_trial_count(5)
400407
routing_pass = SabreSwap(
401408
coupling_map_routing,
402409
heuristic="basic",
403410
seed=seed_transpiler,
404-
trials=5,
411+
trials=trial_count,
405412
)
406413
return common.generate_routing_passmanager(
407414
routing_pass,
@@ -411,11 +418,12 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
411418
use_barrier_before_measurement=True,
412419
)
413420
if optimization_level == 1:
421+
trial_count = _get_trial_count(5)
414422
routing_pass = SabreSwap(
415423
coupling_map_routing,
416424
heuristic="decay",
417425
seed=seed_transpiler,
418-
trials=5,
426+
trials=trial_count,
419427
)
420428
return common.generate_routing_passmanager(
421429
routing_pass,
@@ -429,11 +437,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
429437
use_barrier_before_measurement=True,
430438
)
431439
if optimization_level == 2:
440+
trial_count = _get_trial_count(20)
441+
432442
routing_pass = SabreSwap(
433443
coupling_map_routing,
434444
heuristic="decay",
435445
seed=seed_transpiler,
436-
trials=10,
446+
trials=trial_count,
437447
)
438448
return common.generate_routing_passmanager(
439449
routing_pass,
@@ -446,11 +456,12 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
446456
use_barrier_before_measurement=True,
447457
)
448458
if optimization_level == 3:
459+
trial_count = _get_trial_count(20)
449460
routing_pass = SabreSwap(
450461
coupling_map_routing,
451462
heuristic="decay",
452463
seed=seed_transpiler,
453-
trials=20,
464+
trials=trial_count,
454465
)
455466
return common.generate_routing_passmanager(
456467
routing_pass,
@@ -737,12 +748,15 @@ def _swap_mapped(property_set):
737748
max_trials=2500, # Limits layout scoring to < 600ms on ~400 qubit devices
738749
)
739750
layout.append(ConditionalController(choose_layout_1, condition=_layout_not_perfect))
751+
752+
trial_count = _get_trial_count(5)
753+
740754
choose_layout_2 = SabreLayout(
741755
coupling_map,
742756
max_iterations=2,
743757
seed=pass_manager_config.seed_transpiler,
744-
swap_trials=5,
745-
layout_trials=5,
758+
swap_trials=trial_count,
759+
layout_trials=trial_count,
746760
skip_routing=pass_manager_config.routing_method is not None
747761
and pass_manager_config.routing_method != "sabre",
748762
)
@@ -769,12 +783,15 @@ def _swap_mapped(property_set):
769783
layout.append(
770784
ConditionalController(choose_layout_0, condition=_choose_layout_condition)
771785
)
786+
787+
trial_count = _get_trial_count(20)
788+
772789
choose_layout_1 = SabreLayout(
773790
coupling_map,
774791
max_iterations=2,
775792
seed=pass_manager_config.seed_transpiler,
776-
swap_trials=20,
777-
layout_trials=20,
793+
swap_trials=trial_count,
794+
layout_trials=trial_count,
778795
skip_routing=pass_manager_config.routing_method is not None
779796
and pass_manager_config.routing_method != "sabre",
780797
)
@@ -801,12 +818,15 @@ def _swap_mapped(property_set):
801818
layout.append(
802819
ConditionalController(choose_layout_0, condition=_choose_layout_condition)
803820
)
821+
822+
trial_count = _get_trial_count(20)
823+
804824
choose_layout_1 = SabreLayout(
805825
coupling_map,
806826
max_iterations=4,
807827
seed=pass_manager_config.seed_transpiler,
808-
swap_trials=20,
809-
layout_trials=20,
828+
swap_trials=trial_count,
829+
layout_trials=trial_count,
810830
skip_routing=pass_manager_config.routing_method is not None
811831
and pass_manager_config.routing_method != "sabre",
812832
)
@@ -902,42 +922,50 @@ def _swap_mapped(property_set):
902922
layout = PassManager()
903923
layout.append(_given_layout)
904924
if optimization_level == 0:
925+
trial_count = _get_trial_count(5)
926+
905927
layout_pass = SabreLayout(
906928
coupling_map,
907929
max_iterations=1,
908930
seed=pass_manager_config.seed_transpiler,
909-
swap_trials=5,
910-
layout_trials=5,
931+
swap_trials=trial_count,
932+
layout_trials=trial_count,
911933
skip_routing=pass_manager_config.routing_method is not None
912934
and pass_manager_config.routing_method != "sabre",
913935
)
914936
elif optimization_level == 1:
937+
trial_count = _get_trial_count(5)
938+
915939
layout_pass = SabreLayout(
916940
coupling_map,
917941
max_iterations=2,
918942
seed=pass_manager_config.seed_transpiler,
919-
swap_trials=5,
920-
layout_trials=5,
943+
swap_trials=trial_count,
944+
layout_trials=trial_count,
921945
skip_routing=pass_manager_config.routing_method is not None
922946
and pass_manager_config.routing_method != "sabre",
923947
)
924948
elif optimization_level == 2:
949+
trial_count = _get_trial_count(20)
950+
925951
layout_pass = SabreLayout(
926952
coupling_map,
927953
max_iterations=2,
928954
seed=pass_manager_config.seed_transpiler,
929-
swap_trials=20,
930-
layout_trials=20,
955+
swap_trials=trial_count,
956+
layout_trials=trial_count,
931957
skip_routing=pass_manager_config.routing_method is not None
932958
and pass_manager_config.routing_method != "sabre",
933959
)
934960
elif optimization_level == 3:
961+
trial_count = _get_trial_count(20)
962+
935963
layout_pass = SabreLayout(
936964
coupling_map,
937965
max_iterations=4,
938966
seed=pass_manager_config.seed_transpiler,
939-
swap_trials=20,
940-
layout_trials=20,
967+
swap_trials=trial_count,
968+
layout_trials=trial_count,
941969
skip_routing=pass_manager_config.routing_method is not None
942970
and pass_manager_config.routing_method != "sabre",
943971
)
@@ -957,3 +985,9 @@ def _swap_mapped(property_set):
957985
embed = common.generate_embed_passmanager(coupling_map)
958986
layout.append(ConditionalController(embed.to_flow_controller(), condition=_swap_mapped))
959987
return layout
988+
989+
990+
def _get_trial_count(default_trials=5):
991+
if CONFIG.get("sabre_all_threads", None) or os.getenv("QISKIT_SABRE_ALL_THREADS"):
992+
return max(CPU_COUNT, default_trials)
993+
return default_trials

qiskit/user_config.py

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class UserConfig:
3535
transpile_optimization_level = 1
3636
parallel = False
3737
num_processes = 4
38+
sabre_all_threads = true
3839
3940
"""
4041

@@ -168,6 +169,13 @@ def read_config_file(self):
168169
)
169170
self.settings["num_processes"] = num_processes
170171

172+
# Parse sabre_all_threads
173+
sabre_all_threads = self.config_parser.getboolean(
174+
"default", "sabre_all_threads", fallback=None
175+
)
176+
if sabre_all_threads is not None:
177+
self.settings["sabre_all_threads"] = sabre_all_threads
178+
171179

172180
def set_config(key, value, section=None, file_path=None):
173181
"""Adds or modifies a user configuration
@@ -208,6 +216,7 @@ def set_config(key, value, section=None, file_path=None):
208216
"transpile_optimization_level",
209217
"parallel",
210218
"num_processes",
219+
"sabre_all_threads",
211220
}
212221

213222
if section in [None, "default"]:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
features_transpiler:
3+
- |
4+
Added a new user config file option ``sabre_all_threads`` and a
5+
corresponding environment variable ``QISKIT_SABRE_ALL_THREADS``. When this
6+
flag is set the preset pass managers will run the :class:`.SabreLayout`
7+
and :class:`.SabreSwap` transpiler passes using all the available
8+
CPUs on the local system. Using this option is a tradeoff between
9+
determinism of output between different computers and potentially better
10+
output with fewer :class:`.SwapGate`\s.
11+
12+
These transpiler passes run multiple random trials in parallel and pick
13+
the output which results in the fewest :class:`.SwapGate`\s. As a rule of
14+
thumb, if you run more trials, this provides the algorithm more opportunities
15+
to find a better result. By default, the preset pass managers use a fixed
16+
number of trials, in this release 5 trials for levels 0 and 1, and 20
17+
trials for levels 2 and 3, but these numbers may change in future releases
18+
(and were different in historical releases). Using a fixed number of
19+
trials results in deterministic results regardless of the local system,
20+
because even with a fixed seed if you were to default to the number of
21+
local CPUs available the results would different when running between
22+
different computers.
23+
24+
If the default number of trials for a given optimization level is higher
25+
than the number of local CPUs it will use the optimization level default
26+
which is higher.

0 commit comments

Comments
 (0)