Skip to content

[QDP] Complete Tensor Core IQP path remediation#1387

Open
aloha1357 wants to merge 24 commits into
apache:mainfrom
aloha1357:pr2-batch-throughput-opt
Open

[QDP] Complete Tensor Core IQP path remediation#1387
aloha1357 wants to merge 24 commits into
apache:mainfrom
aloha1357:pr2-batch-throughput-opt

Conversation

@aloha1357

@aloha1357 aloha1357 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Tensor Core IQP path remediation

This PR updates the Tensor Core IQP path from the earlier scaffold into a merge-ready implementation addressing the review feedback on #1387.

The main goals are:

  • remove unused experimental AdaptiveOzakiEngine code
  • fix Tensor Core launcher error propagation
  • validate encode_batch_tc directly with deterministic correctness tests
  • keep Tensor Core behavior explicit opt-in and separate from the default IQP path

What changed

Dead code removal

The previous branch carried an unused AdaptiveOzakiEngine implementation and an unused launch_adaptive_ozaki_gemm FFI surface. That code was not part of the IQP Tensor Core path and made the PR broader than necessary.

This PR removes that path entirely:

  • deleted qdp/qdp-kernels/src/AdaptiveOzaki.cu
  • deleted qdp/qdp-kernels/src/AdaptiveOzaki.h
  • removed AdaptiveOzaki.cu from build.rs
  • removed launch_adaptive_ozaki_gemm FFI declaration and non-CUDA stub from lib.rs
  • extracted the minimal shared Ozaki config into ozaki_config.h

CUDA error handling

The N <= 12 Tensor Core path now checks CUDA failures instead of silently returning success:

  • captures and checks cudaFuncSetAttribute
  • launches the shared-memory TC kernel
  • calls cudaStreamSynchronize(stream)
  • returns any synchronization error
  • otherwise returns the final cudaGetLastError()

Test coverage

The test suite now exercises encode_batch_tc directly instead of only the default encode path.

Test area Coverage
Deterministic inputs seeds 42 and 137
Independent oracle torch_iqp_encode_ref CPU formula reference
Small-N correctness N = 6, 8, 10, 12, max_err < 1e-9
Large-N correctness N = 14, 16, max_err < 1e-5
Existing TC tests preserved and moved to deterministic inputs
Binding smoke tests unchanged behavior verified

Reviewer feedback addressed

Review point Resolution
TC API returns invalid IQP state Direct TC path tests validate against an independent CPU formula oracle
Full-ZZ TC batches read wrong sample data TC path accounts for the full IQP parameter count
Test did not exercise TC path Tests now call encode_batch_tc directly
TC launcher hid CUDA failures cudaFuncSetAttribute is checked; stream is synchronized before final error check
Random-only tests / tolerance unclear Deterministic seeds and documented 1e-9 / 1e-5 tolerances
Scaffolding should not merge to main Branch is now a complete TC-path remediation, not scaffold-only

Path separation

This PR does not introduce the later Native/Fused FP32 Hadamard work. That will be proposed separately as a new explicit opt-in path.

Path Status in this PR
Default IQP encode path preserved
Tensor Core encode_batch_tc path remediated and directly tested
Native/Fused FP32-FP64 path separate future PR

Validation

Validated locally in the repository WSL environment:

Command Result
cargo build -p qdp-kernels passed
cargo test -p qdp-kernels -q passed
cargo clippy -p qdp-kernels -- -D warnings passed
pytest testing/qdp/test_iqp_tc_path.py -v 25 passed
pytest testing/qdp/test_iqp_tc_path.py -q passed
pytest testing/qdp/test_bindings.py -q 80 passed, 2 skipped
pytest testing/qdp/ -q 165 passed, 2 skipped
ruff check qdp/ passed
ty check qdp/ passed

Notes on large N

The implementation is intended to support larger Tensor Core IQP workloads, but the automated CPU-oracle correctness tests intentionally stop at N = 16.

For N = 27, a full state has 2^27 = 134,217,728 amplitudes, so a full CPU-oracle max-error test is not appropriate for this PR gate. Larger-N coverage should use smoke/invariant tests and benchmark-specific validation rather than materializing a full CPU reference in CI.

@aloha1357 aloha1357 force-pushed the pr2-batch-throughput-opt branch 2 times, most recently from 5b0c215 to a3cb616 Compare June 10, 2026 13:15
aloha1357 added 15 commits June 10, 2026 22:49
…tests

- Expose encode_batch_tc through Rust core, PyO3, and Python backend
- Fix IQP TC kernel: correct batch stride for ZZ params and raise
  FWT_SHARED_MEM_THRESHOLD to 12 for fused shared-memory path at N<=12
- Align ImplicitHadamardOzaki.cu with PR6 ldmatrix/alignment fixes
- Add benchmark_pr5.py with --path fwt|tc|both (GPU-vs-GPU, no PyTorch)
- Add test_iqp_tc_path.py smoke and normalization tests
…C GEMM on non-Hadamard logic (PR6). Tests: wsl cargo test passed (0 failures). PR6 comments added.
…r fusion and Kronecker decomposition

This commit introduces the foundational architecture for Tensor Core FWT, including:
- Fused shared-memory kernel for N <= 12 (1.68x speedup).
- Matrix-Free Kronecker decomposition for N > 12 using Adaptive Ozaki GEMM.
- Fixed critical ldmatrix alignment bugs in CUDA kernels.
- Exposed encode_batch_tc to Python API.
- Comprehensive benchmark script and performance report.
…eline fixes

- iqp_tc.cu: 2-step Kronecker path with fused transpose; CHECK_CUDA_RETURN; static buffers
- ImplicitHadamardOzaki: fused-batch MMA + naive FP64 fallback (replace persistent MMA)
- iqp.rs: propagate cu_stream() to launch_iqp_encode_tc
- encode_batch_tc(encoding_method) for iqp / iqp-z
- benchmark_e2e.py: Mahout-Arrow (FWT) vs Mahout-TC with correctness verification
…rify

- naive_hadamard: implement transpose_batch for Kronecker legs (n>=256)
- Ozaki dispatch: INT8 MMA fused TC for n in {64,128}; naive FP64 for n>=256
- iqp_tc: zero temp/out buffers and stream-sync to avoid WDDM flakes
- benchmark_e2e: clone DLPack tensors so FWT/TC states are not aliased
- test_iqp_tc_path: add N=16/17/18 agreement tests (12 cases total)

Verified WSL2 RTX 4060 2026-06-11: pytest 12/12; N=14-18 max_err ~1e-17;
E2E iqp-z N=16 SUCCESS (1.5e-8, ~14.5x vs FWT).
Comment thread testing/qdp/test_batch_throughput.py Outdated
@ryankert01 ryankert01 force-pushed the pr2-batch-throughput-opt branch from c2d9f08 to 0d12958 Compare June 11, 2026 17:02
@aloha1357 aloha1357 force-pushed the pr2-batch-throughput-opt branch from 242e2be to a82426c Compare June 11, 2026 17:31
@aloha1357 aloha1357 closed this Jun 11, 2026
@aloha1357 aloha1357 deleted the pr2-batch-throughput-opt branch June 11, 2026 18:45
@aloha1357 aloha1357 restored the pr2-batch-throughput-opt branch June 11, 2026 18:51
@aloha1357 aloha1357 reopened this Jun 11, 2026
@aloha1357 aloha1357 force-pushed the pr2-batch-throughput-opt branch 2 times, most recently from 2306712 to 0d12958 Compare June 12, 2026 12:12
@ryankert01 ryankert01 force-pushed the pr2-batch-throughput-opt branch from 0d12958 to 6521852 Compare June 15, 2026 11:23

@ryankert01 ryankert01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's several issue found:

  1. TC API returns an invalid IQP state
  2. Full-ZZ TC batches read the wrong sample data
  3. The new test does not exercise the TC path
  4. TC launcher hides CUDA allocation and launch failures
  5. and some test enhancements

Comment thread testing/qdp/test_batch_throughput.py Outdated
Comment on lines +64 to +65
rtol=1e-10,
atol=1e-10,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

with different precision can have different tolerances(f32,f64)

Comment thread testing/qdp/test_batch_throughput.py Outdated
method = "iqp-z"

# Generate random parameters (batched)
data = torch.randn(batch_size, n_params, dtype=torch.float64, device="cuda")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not use truely random numbers because it introduces flackiness.

Comment thread testing/qdp/test_batch_throughput.py Outdated
Comment on lines +31 to +32
@pytest.mark.parametrize("n_qubits", [2, 4, 6])
@pytest.mark.parametrize("batch_size", [1, 16, 64])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add weird n_qubits and batch_size for robustness.

@ryankert01 ryankert01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few comments

Comment thread qdp/qdp-kernels/src/iqp_tc.cu Outdated

// In future PRs, Kronecker Transpose and FWT will happen here.

recombine_complex_kernel<<<blocks, DEFAULT_BLOCK_SIZE, 0, stream>>>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This TC launcher currently writes the pre-FWT phase vector rather than an IQP-encoded state. iqp_phase_split_kernel computes exp(i theta(x)), and this recombine writes it directly to the output; the FWT + normalization is still marked as future work. For all-zero params, this returns all ones instead of |0...0>. Can we either keep this as private scaffolding for now, or complete the transform before exposing it through encode_batch_tc?

Comment thread qdp/qdp-kernels/src/iqp_tc.cu Outdated
cudaMalloc(&d_state_real, total_elements * sizeof(double));
cudaMalloc(&d_state_imag, total_elements * sizeof(double));

unsigned int data_len = num_qubits;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For full ZZ batches, this hardcodes data_len = num_qubits, but Rust validates sample_size as num_qubits + num_qubits * (num_qubits - 1) / 2. That means sample 1 starts inside sample 0's ZZ parameters when enable_zz=true. Can we pass the actual sample_size/data length into launch_iqp_encode_tc, matching the existing batch launcher?

Comment thread testing/qdp/test_batch_throughput.py Outdated
)

# 2. QDP Engine (Testing the Batch throughput C++ / Rust API path)
actual_state_dlpack = engine.encode(data, n_qubits, encoding_method=method)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test currently exercises engine.encode(...), which routes through the existing standard IQP batch path, not the new encode_batch_tc path. So it can pass even if the TC scaffold is incorrect. Can we add a direct CUDA/Rust test for IqpEncoder::encode_batch_tc, or expose an explicit opt-in Python API and test that path?

Comment thread qdp/qdp-kernels/src/iqp_tc.cu Outdated
size_t total_elements = num_samples * state_len;

double *d_state_real, *d_state_imag;
cudaMalloc(&d_state_real, total_elements * sizeof(double));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This launcher ignores both cudaMalloc return values and then returns cudaSuccess unconditionally. On OOM or launch failure, Rust will report success while the output may be invalid; if the second allocation fails, the first allocation can also leak. Can we return CUDA errors consistently with the existing IQP launchers and free any partial allocations on failure?

@ryankert01

ryankert01 commented Jun 22, 2026

Copy link
Copy Markdown
Member

Btw, @aloha1357 Thanks for your patch!

@ryankert01 ryankert01 force-pushed the pr2-batch-throughput-opt branch from 6521852 to 44f5aca Compare June 22, 2026 08:49
@aloha1357

aloha1357 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@ryankert01 Thanks for the detailed review — these are exactly the right things to flag on this PR.

Context: intentional scaffolding

#1387 (PR2) is scoped as TC-path scaffolding only: encode_batch_tc API wiring,
phase split (real/imag), and bank-conflict-free batch transpose. The full Hadamard /
Kronecker / Tensor Core pipeline is delivered incrementally in the stacked PRs below,
so each PR stays reviewable on its own.

Where your points are addressed
Reviewer issues vs. PR stack

# Reviewer Issue PR2 #1387 PR4 #1391 PR5 #1390 PR6 #1389 / PR7
Q1 TC API returns invalid IQP state (no FWT / not a proper quantum state) Scaffolding only — phase split → recombine; no Hadamard Partially fixed — shared-mem fused FWT (N≤12) + Kronecker naive-GEMM pipeline (N>12); still broken for full-ZZ until PR5 Fixed — full pipeline; Ozaki implicit Hadamard replaces naive GEMM Fixed — same as PR5; WMMA / TC kernels hardened
Q2 Full-ZZ TC batches read wrong sample data (data_len = num_qubits only) Not fixed — hardcoded data_len = num_qubits Not fixed — same bug in launch_iqp_encode_tc Fixeddata_len accounts for ZZ params (commit 806a419) Fixed — inherited from PR5
Q3 Tests do not exercise the TC path (engine.encode() instead of encode_batch_tc) Not fixedtest_batch_throughput.py calls engine.encode() only Not fixed — no TC-specific test; Rust encode_batch_tc exists but Python API not exposed yet Fixedtest_iqp_tc_path.py calls encode_batch_tc() Fixed — extended (e.g. N=14 FWT vs TC smoke / loose agreement)
Q4 TC launcher hides CUDA alloc/launch failures (always return cudaSuccess) Not fixed — hardcoded cudaSuccess; cudaMalloc unchecked Not fixed — same Partially fixedcudaFuncSetAttribute error check + cudaGetLastError() at exit; cudaMalloc return values still unchecked Partially fixed — same as PR5; cudaMalloc checks + buffer pool planned for PR6/PR7
Q5 Test quality (random data; tolerance should follow precision) Not addressedtorch.randn + assert_close on wrong API (encode) Not addressed — no TC tests Partially addressedencode_batch_tc normalization smoke tests; still uses torch.randn; no torch_ref baseline for TC correctness Partially addressed — adds FWT-vs-TC comparison (loose tolerance); deterministic data + precision-aware torch_ref baseline still planned for PR6/PR7

One-Line Summary per PR

PR Q1–Q5 Coverage
PR2 Scaffolding; all five issues are visible — expected at this stage
PR4 Q1 partially fixed; Q2–Q5 still open
PR5 Q1–Q3 fixed; Q4–Q5 partial
PR6/PR7 Q1–Q3 done; Q4–Q5 to be completed here

Note: PR3 (#1388) is a separate line (shared-mem FWT in iqp.cu only; no iqp_tc.cu). It does not address Q1–Q5 for the TC path discussed in #1387.
Stack for review

Question for you

If the PR2 scaffolding looks acceptable in principle, would you prefer to:

  1. Approve PR2 as scaffolding and continue reviewing PR4 → PR5 → PR6 in order, or
  2. Hold final feedback until PR6/PR7, where we will land the remaining Q4/Q5 fixes
    (cudaMalloc error propagation, deterministic/precision-aware tests)?

Option 2 avoids repeatedly growing the same PR and keeps the review surface stable.
Happy to adjust based on what works best for your review workflow.

@ryankert01

Copy link
Copy Markdown
Member

Im object to merge any scaffolding pr into main. We can do a dev branch dedicate for this or a big pr.

@aloha1357 aloha1357 force-pushed the pr2-batch-throughput-opt branch from 44f5aca to ac78832 Compare July 1, 2026 19:47
@aloha1357

Copy link
Copy Markdown
Contributor Author

Thanks, that makes sense. I agree that merging a
scaffolding-only PR into main is not the right direction.

I reworked this branch following the “big PR” approach you
suggested, so this is no longer just TC scaffolding. The
branch now contains the complete remediation for the TC path
review issues.

What changed:

  • Removed the unused AdaptiveOzakiEngine path entirely:

    • deleted AdaptiveOzaki.cu
    • deleted AdaptiveOzaki.h
    • removed AdaptiveOzaki.cu from build.rs
    • removed the unused launch_adaptive_ozaki_gemm FFI
      declaration and non-CUDA stub
  • Extracted only the shared Ozaki config types needed by
    ImplicitHadamardOzakiEngine into ozaki_config.h.

  • Fixed the TC launcher error handling:

    • cudaFuncSetAttribute return value is now checked
    • the N <= 12 path now calls
      cudaStreamSynchronize(stream) before the final
      cudaGetLastError()
    • the larger path continues to use checked CUDA calls
  • Fixed the Full-ZZ sample data issue:

    • TC data length now accounts for ZZ parameters instead of
      assuming data_len = num_qubits
  • Added direct TC-path tests:

    • tests now call encode_batch_tc
    • deterministic seeds: 42 and 137
    • added independent CPU formula oracle
      torch_iqp_encode_ref
    • small-N checks: N = 6, 8, 10, 12, max_err < 1e-9
    • large-N checks: N = 14, 16, max_err < 1e-5
    • existing TC smoke/agreement tests are preserved and now
      use deterministic inputs

    Validation run locally in the repo WSL environment:

cargo build -p qdp-kernels
cargo test -p qdp-kernels -q
cargo clippy -p qdp-kernels -- -D warnings
pytest testing/qdp/test_iqp_tc_path.py -v
pytest testing/qdp/test_iqp_tc_path.py -q
pytest testing/qdp/test_bindings.py -q
pytest testing/qdp/ -q
ruff check qdp/
ty check qdp/

Results:

pytest testing/qdp/test_iqp_tc_path.py -v: 25 passed
pytest testing/qdp/test_bindings.py -q: 80 passed, 2 skipped
pytest testing/qdp/ -q: 165 passed, 2 skipped
ruff check qdp/: passed
ty check qdp/: passed
cargo build/test/clippy: passed

So the branch is now intended to be reviewed as the complete
TC-path PR, not as scaffolding.

@aloha1357 aloha1357 changed the title [QDP] Pr2 batch throughput opt [QDP] Complete Tensor Core IQP path remediation Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants