Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,20 @@ pub fn getenv_use_multiple_threads() -> bool {
.unwrap_or_else(|_| "FALSE".to_string())
.to_uppercase()
== "TRUE";
!parallel_context || force_threads

let result = !parallel_context || force_threads;

// Log threading decision if debug logging is enabled
if env::var("QISKIT_DEBUG_THREADING").is_ok() {
Comment thread
eginez marked this conversation as resolved.
Outdated
eprintln!(
Copy link

Copilot AI Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using eprintln! for debug output is inconsistent with typical Rust logging practices. Consider using the log crate to emit debug-level messages and integrate with existing logging infrastructure.

Copilot uses AI. Check for mistakes.
"Rust threading decision: {} (parallel_context={}, force_threads={})",
if result { "MULTI_THREADED" } else { "SINGLE_THREADED" },
parallel_context,
force_threads
);
}

result
}

import_exception!(qiskit.exceptions, QiskitError);
Expand Down
12 changes: 10 additions & 2 deletions qiskit/passmanager/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import dill

from qiskit.utils.parallel import parallel_map, should_run_in_parallel
from qiskit.utils.parallel import parallel_map, should_run_in_parallel, CPU_COUNT
from .base_tasks import Task, PassManagerIR
from .exceptions import PassManagerError
from .flow_controllers import FlowControllerLinear
Expand Down Expand Up @@ -227,7 +227,12 @@ def callback_func(**kwargs):

# If we're not going to run in parallel, we want to avoid spending time `dill` serializing
# ourselves, since that can be quite expensive.
if len(in_programs) == 1 or not should_run_in_parallel(num_processes):
use_parallel = should_run_in_parallel(num_processes)
if len(in_programs) == 1 or not use_parallel:
if len(in_programs) == 1:
logger.debug("PassManager running single program serially")
else:
logger.debug("PassManager running %d programs serially (parallel disabled)", len(in_programs))
out = [
_run_workflow(program=program, pass_manager=self, callback=callback, **kwargs)
for program in in_programs
Expand All @@ -239,6 +244,9 @@ def callback_func(**kwargs):
del callback
del kwargs

logger.debug("PassManager running %d programs in parallel with %d processes",
Copy link

Copilot AI Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serial execution branch assigns to out but lacks a return, so execution always falls through to the parallel path. Add a return statement inside the if-block to avoid unintended parallel runs.

Copilot uses AI. Check for mistakes.
len(in_programs), num_processes or CPU_COUNT)

# Pass manager may contain callable and we need to serialize through dill rather than pickle.
# See https://github.com/Qiskit/qiskit-terra/pull/3290
# Note that serialized object is deserialized as a different object.
Expand Down
15 changes: 12 additions & 3 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@

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

import logging
import os
import warnings

logger = logging.getLogger(__name__)

from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries
from qiskit.transpiler.passmanager import PassManager
from qiskit.transpiler.exceptions import TranspilerError
Expand Down Expand Up @@ -1024,6 +1027,12 @@ def _swap_mapped(property_set):


def _get_trial_count(default_trials=5):
if CONFIG.get("sabre_all_threads", None) or os.getenv("QISKIT_SABRE_ALL_THREADS"):
return max(CPU_COUNT, default_trials)
return default_trials
use_all_threads = CONFIG.get("sabre_all_threads", None) or os.getenv("QISKIT_SABRE_ALL_THREADS")
Comment thread
eginez marked this conversation as resolved.
if use_all_threads:
trial_count = max(CPU_COUNT, default_trials)
logger.debug("SABRE using all threads: %d trials (CPU_COUNT=%d, default=%d)",
trial_count, CPU_COUNT, default_trials)
return trial_count
else:
logger.debug("SABRE using default thread configuration: %d trials", default_trials)
return default_trials
26 changes: 23 additions & 3 deletions qiskit/utils/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

from __future__ import annotations

import logging
import os
from concurrent.futures import ProcessPoolExecutor
import sys
Expand All @@ -58,6 +59,8 @@
from qiskit.utils.multiprocessing import local_hardware_info
from qiskit import user_config

logger = logging.getLogger(__name__)


def get_platform_parallel_default():
"""
Expand Down Expand Up @@ -111,11 +114,24 @@ def should_run_in_parallel(num_processes: int | None = None) -> bool:
num_processes: the number of processes requested for use (if given).
"""
num_processes = CPU_COUNT if num_processes is None else num_processes
return (
in_parallel = os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "TRUE"
parallel_enabled = CONFIG.get("parallel_enabled", PARALLEL_DEFAULT)

result = (
num_processes > 1
and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE"
and CONFIG.get("parallel_enabled", PARALLEL_DEFAULT)
and not in_parallel
and parallel_enabled
)

logger.debug(
"Parallelization decision: %s (num_processes=%d, in_parallel=%s, parallel_enabled=%s)",
"PARALLEL" if result else "SERIAL",
num_processes,
in_parallel,
parallel_enabled
)

return result


def parallel_map( # pylint: disable=dangerous-default-value
Expand Down Expand Up @@ -164,6 +180,7 @@ def func(_):
return [task(values[0], *task_args, **task_kwargs)]

if should_run_in_parallel(num_processes):
logger.debug("Executing parallel_map with %d processes for %d items", num_processes, len(values))
os.environ["QISKIT_IN_PARALLEL"] = "TRUE"
try:
results = []
Expand All @@ -172,8 +189,10 @@ def func(_):
future = executor.map(_task_wrapper, param)

results = list(future)
logger.debug("Parallel execution completed successfully")

except (KeyboardInterrupt, Exception) as error:
logger.debug("Parallel execution failed: %s", str(error))
if isinstance(error, KeyboardInterrupt):
os.environ["QISKIT_IN_PARALLEL"] = "FALSE"
raise QiskitError("Keyboard interrupt in parallel_map.") from error
Expand All @@ -184,6 +203,7 @@ def func(_):
os.environ["QISKIT_IN_PARALLEL"] = "FALSE"
return results

logger.debug("Executing parallel_map serially for %d items", len(values))
results = []
for _, value in enumerate(values):
result = task(value, *task_args, **task_kwargs)
Expand Down