양자 오류 수정(QEC)은 오늘날 양자 컴퓨팅 분야에서 가장 활발하게 연구되고 있는 분야 중 하나입니다. QEC의 목표는 큐비트에 저장된 매우 민감한 양자 정보를 잡음과 결맞음 상실로부터 보호하는 것입니다.

큐비트의 신뢰도는 오류 확률을 기준으로 측정되며, 현재 물리적 큐비트는 게이트 연산 10,000회당 약 1회(10⁻⁴)의 오류 발생률을 달성할 수 있습니다. 그러나 완전한 효율성을 위해서는 게이트 오류율이 최소 10억 분의 1(10⁻⁹) 또는 1조 분의 1(10⁻¹²) 수준에 도달해야 합니다.

본 문서는 양자 오류 수정(QEC)을 구현 관점에서 이해하기 위한 간략하고 쉬운 안내서입니다. 첫 번째 섹션에서는 논리 큐비트를 생성하는 데 필요한 다양한 오류 수정 계산 방법을 개괄적으로 설명합니다. 두 번째 섹션에서는 논리적으로 수정된 큐비트에 대한 게이트 연산의 구현 가능성을 살펴봅니다. 마지막 섹션에서는 Cuda-QEC 프레임워크와 이를 활용한 양자 오류 수정 시스템 구축 방법을 소개합니다.

QEC는 무엇을 해결하려고 하며, 어떻게 해결하려는가? Link to heading

양자 오류 수정(QEC)의 과제는 언뜻 보기에 간단합니다. 오류가 발생했을 때 이를 감지하고 수정하는 것입니다. 하지만 이 과정은 큐비트의 양자 상태를 측정하거나 붕괴시키지 않고 이루어져야 합니다. 바로 이 지점에서 얽힌 보조 큐비트라는 개념이 등장합니다.

양자 오류 수정(QEC)은 정보를 단일 큐비트에 저장하는 대신, 데이터 큐비트와 보조 큐비트(측정 큐비트라고도 함)의 두 가지 범주로 나뉜 여러 물리적 큐비트에 걸쳐 하나의 논리 큐비트를 인코딩합니다. 이 큐비트 그룹은 얽힌 상태를 형성하여 데이터 큐비트 상태를 붕괴시키지 않고 보조 큐비트를 읽어낼 수 있게 됩니다. 아래 그림에서 빨간색 선은 여러 가지 가능한 논리 큐비트와 물리적 큐비트의 매핑을 보여줍니다.

작동 방식 Link to heading

여러 개의 물리적 큐비트로 인코딩된 논리 큐비트를 오류 정정한다고 가정해 보겠습니다. 오류가 발생하면 물리적 큐비트 중 하나가 비트 반전(X), 위상 반전(Z) 또는 둘 다(Y)의 형태로 반전될 수 있습니다.

QEC는 플립 현상을 확인하기 위해 주기적인 *신드롬 측정*을 수행하는데, 이는 데이터 큐비트와 상호작용하는 특정 보조 큐비트를 측정하는 과정을 포함합니다. 보조 큐비트는 먼저 데이터 큐비트와 얽힘 상태가 된 후 판독이 수행됩니다. 얽힘 상태는 판독이 수행될 수 있을 만큼 짧은 시간 동안만 유지됩니다. 구글의 경우, 이러한 얽힘은 CZ(컨트롤-Z) 게이트를 사용하여 구현되는데, 이 게이트 또한 얽힘을 이용합니다.

보조 큐비트 측정> (이미지 출처: Google Quantum AI - Implementing surface code logical qubits)

증상이 파악되면 보정을 수행할 수 있습니다. 이를 위해 고전적인 알고리즘이 발생했을 가능성이 있는 오류를 판별합니다. 그런 다음 시스템은 관련 보정(X, Y 또는 Z 게이트)을 적용합니다. Google의 경우 위상차(T2?) 오류를 완화하기 위해 추가적인 동적 디커플링(DD)이 사용됩니다.

양자 코드 Link to heading

데이터 큐비트와 측정 큐비트는 비국소적으로 분포되어 있어 하나 또는 두 개의 큐비트에서 오류가 발생하더라도 그룹 일관성이 파괴되지 않습니다. 이러한 분포 방식을 양자 코드(quantum code)라고 합니다. 실제 시스템에서는 더 크고 정교한 코드를 사용합니다.

  • Shor 코드 (9 큐비트): 비트 반전 오류와 위상 반전 오류를 모두 수정합니다.

  • 스테인 코드 (7 큐비트): 최초의 완전 양자 오류 정정 코드.

  • 표면 코드 (\(2n^2-1\) 큐비트) - 큐비트를 2차원 격자에 배열합니다. 표면 코드 거리 \(n\)은 동시에 수정할 수 있는 오류의 수를 나타냅니다.

실제로 각 측정 큐비트는 고전 알고리즘의 결정 논리에 사용되는 안정화 연산자를 투영적으로 측정하는 역할을 합니다. 안정화 코드는 고전 오류 정정에서 사용되는 선형 코드의 양자 일반화로서, 패리티 검사를 통해 노이즈 비트의 오류를 검출합니다.

논리 큐비트에서 양자 알고리즘을 실행하는 방법은 무엇인가요?

물리적 큐비트에 양자 알고리즘을 실행한다는 것은 물리적 큐비트에 게이트를 적용하는 것을 의미합니다. 그렇다면 논리적 큐비트를 다룰 때는 어떨까요? 간단해 보이는 질문이지만, 답은 그리 간단하지 않으며 논리적 큐비트 게이트를 구현하는 방법은 여러 가지가 있습니다. 먼저 단일 큐비트 게이트부터 살펴보고, 나중에 다중 큐비트 게이트 문제를 다뤄보겠습니다.

단일 큐비트 게이트를 논리 큐비트에 적용하기 Link to heading

논리 게이트 구현 유형:

  • 횡단 게이트: 횡단 게이트는 각 물리적 큐비트에 독립적으로 작용합니다.

논리 큐비트 게이트는 n개의 물리적 큐비트 각각에 동일한 게이트를 적용하는 것과 같습니다. 예를 들어, 7큐비트 스틴 코드에서 논리 하드마르 게이트는 7개의 물리적 큐비트 각각에 물리적 H 게이트를 적용하여 구현됩니다.

표면 코드에서 논리 큐비트는 물리적 큐비트의 큰 패치로 인코딩됩니다. 논리 연산은 패치를 병합하거나 분할하거나 결함을 연결하여 구현되며, 이로 인해 코드의 토폴로지가 변경됩니다.

arxiv.2505.15907 의 오른쪽 이미지는 두 코드 패치를 가장자리를 따라 결합하여 격자 수술 작업을 수행하는 것을 보여줍니다. 이 작업은 내결함성을 달성하기 위해 O(d) 라운드가 필요합니다.

논리 큐비트에 다중 큐비트 게이트 적용 Link to heading

  • 횡단 다중 큐비트 게이트 : 많은 양자 코드(예: 스틴, 베이컨-쇼어)에서 논리적 CNOT는 횡단 방식으로 구현할 수 있습니다. \( CNOT_L​=\sum_{i=1}^n ​CNOT(a_i​,b_i​) \)

즉, 제어 블록의 모든 물리적 큐비트 \(a_i\)에 대해 대상 블록의 해당 큐비트 \(b_i\)에 물리적 CNOT 연산을 적용합니다.

예를 들어, CCNOT(토폴리) 게이트는 대부분의 코드에서 횡단적으로 구현할 수 없습니다(*). 대신, 조건부로 “텔레포트"된 상태인 매직 상태를 생성하여 구현합니다. 예를 들어 CNOT 게이트를 사용할 때 텔레포트 게이트가 사용됩니다. 이 경우 텔레포트 게이트는 논리 게이트 연산이라는 점에 유의하십시오.

예를 들어, CNOT는 격자 수술을 통해, CZ는 땋기를 통해 구현됩니다.

요약 Link to heading

다중 큐비트 게이트를 논리 큐비트에 적용하는 것은 코드 구조에 따라 인코딩된 물리적 큐비트에 연산을 가함으로써 이루어집니다.

  • 횡단적 게이트: 논리 블록의 물리적 큐비트 사이의 쌍별 게이트.

  • 보조 시스템: 순간 이동 또는 마법 상태 방법을 사용합니다.

  • 위상수학: 꼬임이나 격자 수술과 같은 기하학적 연산을 수행합니다.

여기까지 읽어주신 독자분들께 사과드립니다. 이 부분은 다소 가볍고, 심도 있는 탐구라기보다는 표면적인 내용에 그치고 있습니다. 지금 알아두셔야 할 것은 논리 큐비트의 단일/다중 큐비트 게이트에 대한 단 하나의 해법이 있는 것이 아니라, 각 게이트와 양자 코드 유형에 적용할 수 있는 여러 가지 방법론이 존재한다는 것입니다.

프로그래밍 방식: CUDA-QX QEC 프레임워크 Link to heading

2024년, 엔비디아의 퀀텀 팀은 오픈 소스 CUDA-Q 플랫폼의 확장인 CUDA-QX를 소개하며 양자 개발자를 위한 최적화된 라이브러리를 제공했습니다. CUDA-QX에 포함된 라이브러리 중 두 가지는 CUDA-Q QEC(양자 오류 수정 기본 요소 및 디코더)와 CUDA-Q Solvers(VQE/ADAPT-VQE, QAOA 헬퍼)입니다.

안정화 연산자의 고전적 논리에 대한 사용자 정의 구현을 위한 프로그래밍 API를 제공하는 방식을 간단히 살펴보겠습니다. 아래는 단일 큐비트 비트 플립 오류를 감지하는 데 사용되는 가장 간단한 디코더를 구현한 최소한의 예제입니다. 이 디코더는 신드롬이 패리티 행렬 Hz의 단일 열과 일치하는지 확인하는 방식으로 작동하며, 일치하는 경우 큐비트가 플립된 것으로 감지됩니다.

import cudaq_qec as qec

def run_simple_code_capacity( nShots=1000, errorProbability=0.1 ):
    
    steane = qec.get_code("steane")         # get built-in Steane code from CUDA-Q QEC
    parity_check_matrix = steane.get_parity_z()              # parity-check matrix for Z (bit-flip) errors
    observable = steane.get_observables_z() # Get the Pauli Z observables of the code

    decoder = MyCustomDecoder(Hz) # instantiate custom decoder

    nLogicalErrors = 0
    for _ in range(nShots):
        # Generate noisy data
        data = qec.generate_random_bit_flips(Hz.shape[1], errorProbability) 
        
        # Calculate which syndromes are flagged.
        syndrome = (parity_check_matrix @ data) % 2

        # Decode the syndrome to predict what happened to the data
        result = decoder.decode(syndrome)
        data_prediction = np.array(result.result, dtype=np.uint8)

        # See if this prediction flipped the observable
        predicted_observable = (observable @ data_prediction) % 2
        
        # See if the observable was actually flipped
        actual_observable = observable @ data % 2
        if (predicted_observable != actual_observable):
            nLogicalErrors += 1

    return nLogicalErrors

위 알고리즘에서 중요한 부분은 패리티 행렬과 예측된 관측량이 미리 정의된 스테인 양자 코드로부터 얻어진다는 점입니다. 따라서 간단한 디코더는 다음과 같이 구현할 수 있습니다.

class SingleColumnMatchDecoder:
    def __init__(self, Hz):  # Hz is a parity-check matrix shape (nChecks, nDataQubits)
        self.Hz = np.array(Hz) % 2
        self.n_qubits = self.Hz.shape[1]

    class SimpleDecoderResult:
        def __init__(self):
            self.result = np.zeros(self.n_qubits, dtype=np.uint8)
        def flipped(self, q):
            self.result[q]=1
            return self

    def decode(self, syndrome):
        # ensure syndrome is 1D numpy array of length nChecks
        s = np.asarray(syndrome).flatten() % 2

        # Initialize the flip prediction list
        prediction = SimpleDecoderResult()

        # Compare to each column of Hz: if any column equals the syndrome, pick that qubit
        for q in range(self.n_qubits):
            col = self.Hz[:, q] % 2
            if np.array_equal(col, s):
                return prediction.flipped(q)

        # no single-column match -> predict no flips (all zeros)
        return prediction

디코더가 준비되었으므로 이제 알고리즘을 실행할 수 있습니다.

nShots = 1000
nLogicalErrors = run_simple_code_capacity(nShots)
print(f"Shots = {nShots}, Logical error rate ≈ {nLogicalErrors/nShots:.4f}")

보시다시피, CUDA QEC는 기본적으로 QEC의 고전적 연산 논리를 구현하는 효율적인 도구와 라이브러리를 제공하는 데 중점을 두고 있습니다. 여기서 남은 질문은 이 CUDA QEC가 실제 양자 스택에 어떻게 연결될 수 있는지, 특히 제어 스택에서 QEC 디코더로 신드롬이 어떻게 전달되는지, 그리고 디코더가 제어 스택에 적용해야 할 보정 게이트를 어떻게 알려주는지입니다. 바로 이 부분에서 흥미로운 CUDA-Q 프레임워크가 중요한 역할을 하지만, 이 부분은 추후 메모에서 자세히 다루도록 하겠습니다!

결론 Link to heading

자, 보시다시피 양자 오류 수정의 세계를 깊이 파고드는 것은 상당한 시간과 노력이 필요한 일이므로, 양자 컴퓨팅 아키텍처 분야에서 꾸준히 발전하려면 높은 수준을 유지해야 할 것 같습니다. 지금으로서는 “논리적으로 정확한” 양자 컴퓨팅이라고 해야 할지, 아니면 “신뢰할 수 있는” 양자 컴퓨팅이라고 해야 할지 고민이 됩니다. 전자의 경우, 기계를 정확하게 만드는 모델에 초점을 맞추는 반면, 후자는 기계의 동작에 초점을 맞추기 때문입니다.

이전 게시물(/posts/executing-a-quantum-algorithm/)에서 언급했듯이, 양자 오류 수정(QEC)이 양자 알고리즘에 대한 장식으로 적용되어야 하는지, 즉 양자 게이트 시퀀스를 제어 스택에서 실행 가능한 형태로 변환하는 컴파일러가 표면 코드 출력을 얻기 위해 게이트 연산을 “인라인"하는 책임도 져야 하는지, 아니면 “신뢰할 수 있는 양자 컴퓨팅 시스템"의 “백그라운드” 연산으로 수행되어야 하는지가 저에게는 불분명합니다.


메모 Link to heading

  1. Ancillary와 Auxiliary 중 어떤 단어를 사용해야 할지 고민된다면, Ancillary(라틴어 ancilla: 하녀)가 올바른 단어입니다. Ancillary는 “주요 기능을 지원하는 데 필요한 도움을 제공하는"이라는 의미이고, Auxiliary(라틴어 auxilium: 도움)는 “추가적인 도움을 제공하거나 지원을 아끼지 않는"이라는 의미입니다.

이 메모는 최초 메모 작성 후에 추가되었습니다.

  1. IEEE QCE24에서 “양자 오류 수정 및 최적 제어를 위한 GPU와 QPU의 긴밀한 통합"에 대한 흥미로운 튜토리얼이 있습니다. 해당 튜토리얼의 영상은 여기에서 확인할 수 있으며, 위의 미해결 질문들에 대한 많은 해답과 통찰력을 제공하는 것으로 보입니다. 이 튜토리얼은 추후 메모에서 자세히 다루겠습니다. 현재로서는 회로를 주어진 양자 코드에 맞게 재작성하고, 표면 수준 토폴로지에 맞게 단순화한 다음, 이를 허용하는 게이트(클리포드 게이트)에 대해 후처리하는 것이 핵심 아이디어입니다.

쇼어 알고리즘과 양자 오류 수정(QEC)의 결합


References Link to heading