Qiskit으로 외부 양자 리소스 통합하기
Qiskit SDK는 서드파티가 외부 양자 리소스 프로바이더를 만들 수 있도록 지원하기 위해 설계되었습니다.
즉, 양자 컴퓨팅 리소스를 개발하거나 배포하는 모든 조직은 자신들의 서비스를 Qiskit에 통합하여 그 사용자 기반을 활용할 수 있습니다.
이를 위해서는 양자 컴퓨팅 리소스 요청을 지원하고 그 결과를 사용자에게 반환하는 패키지를 만들어야 합니다.
또한, 해당 패키지는 사용자가 qiskit.primitives 객체의 구현을 통해 작업(job)을 제출하고 결과를 조회할 수 있도록 해야 합니다.
Backend에 대한 접근 제공하기
사용자가 외부 리소스를 사용하여 QuantumCircuit 객체를 Transpiler로 처리하고 실행하려면, QPU의 연결성, 기저 Gate, Qubit 수와 같은 제약 조건에 대한 정보를 제공하는 Target을 포함한 객체를 인스턴스화해야 합니다. 이는 사용자가 QPU를 요청할 수 있는 QiskitRuntimeService와 유사한 인터페이스를 통해 제공할 수 있습니다. 이 객체는 최소한 Target을 포함해야 하지만, 더 간단한 방법은 BackendV2 인스턴스를 반환하는 것입니다.
구현 예시는 다음과 같습니다:
from qiskit.transpiler import Target
from qsikit.providers import BackendV2
class ProviderService:
""" Class for interacting with a provider's service"""
def __init__(
self,
#Receive arguments for authentication/instantiation
):
""" Initiate a connection with the provider service, given some method
of authentication """
def return_target(name: Str) -> Target:
""" Interact with the service and return a Target object """
return target
def return_backend(name: Str) -> BackendV2:
""" Interact with the service and return a BackendV2 object """
return backend
실행을 위한 인터페이스 제공하기
하드웨어 구성을 반환하는 서비스 제공 외에도, 외부 QPU 리소스에 대한 접근을 제공하는 서비스는 양자 워크로드 실행도 지원할 수 있습니다. 해당 기능을 노출하려면 Qiskit primitives 인터페이스의 구현체를 만들면 됩니다. 예를 들어 BasePrimitiveJob, BaseEstimatorV2, BaseSamplerV2 등이 있습니다. 이러한 인터페이스는 최소한 실행 메서드, 작업 상태 조회, 작업 결과 반환 기능을 제공해야 합니다.
작업 상태와 결과를 처리하기 위해 Qiskit SDK는 DataBin, PubResult, PrimitiveResult, BasePrimitiveJob 객체를 제공하며, 이를 사용해야 합니다.
자세한 내용은 qiskit.primitives API 문서와 참조 구현체인 BackendEstimatorV2 및 BackendSampleV2를 참고하세요.
Estimator primitive의 구현 예시는 다음과 같습니다:
from qiskit.primitives import BaseEstimatorV2, BaseSamplerV2, EstimatorPubLike
from qiskit.primitives import DataBin, PubResult, PrimitiveResult, BasePrimitiveJob
from qiskit.providers import BackendV2
class EstimatorImplementation(BaseEstimatorV2):
""" Class for interacting with the provider's Estimator service """
def __init__(
self,
*,
backend: BackendV2,
options: dict
# Receive other arguments to instantiate an Estimator primitive with the service
):
self._backend = backend
self._options = options
self._default_precision = 0.01
@property
def backend(self) -> BackendV2:
""" Return the backend """
return self._backend
def run(
self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None
) -> BasePrimitiveJob[PrimitiveResult[PubResult]]:
""" Steps to implement:
1. Define a default precision if none is given
2. Validate pub format
3. Instantiate an object which inherits from BasePrimitiveJob
containing pub and runtime information
4. Send the job to the execution service of the provider
"""
job = BasePrimitiveJob(pubs, precision)
job_with_results = job.submit()
return job_with_results
Sampler primitive의 구현 예시는 다음과 같습니다:
class SamplerImplementation(BaseSamplerV2):
""" Class for interacting with the provider's Sampler service """
def __init__(
self,
*,
backend: BackendV2,
options: dict
# Receive other arguments to instantiate an Estimator primitive with the service
):
self._backend = backend
self._options = options
self._default_shots = 1024
@property
def backend(self) -> BackendV2:
""" Return the Sampler's backend """
return self._backend
def run(
self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
) -> BasePrimitiveJob[PrimitiveResult[SamplerPubResult]]:
""" Steps to implement:
1. Define a default number of shots if none is given
2. Validate pub format
3. Instantiate an object which inherits from BasePrimitiveJob
containing pub and runtime information
4. Send the job to the execution service of the provider
5. Return the data in some format
"""
job = BasePrimitiveJob(pubs, shots)
job_with_results = job.submit()
return job_with_results