【代码贡献】QSVD: skip the unused state-vector simulation in the loss inner loop - #44
Open
mnn31 wants to merge 1 commit into
Open
【代码贡献】QSVD: skip the unused state-vector simulation in the loss inner loop#44mnn31 wants to merge 1 commit into
mnn31 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
example/QAlgBase/testeg_QSVD.pytakes minutes to finish. #39 reports the samething independently (>300 s on that machine) and deliberately leaves it for
separate handling, which is what this PR does. On my machine the unmodified
example runs 137 s.
#39 also edits
QSVD.py, removing an unusedmachinelocal inmax_eig. Thetwo hunks are about ten lines apart and merge cleanly; I verified against #39's
head: automatic merge, 21 tests pass, the example runs in 22 s.
Root cause
SVD.losscomputes a full state-vector simulation unconditionally, but onlythe
return_type=Falsebranch reads it:QSVD_mindriveslossthrough SLSQP with a numerical jacobian, so the scalarbranch is the inner loop: 4952 evaluations for an 8x8 matrix (48 parameters,
100 iterations). Every one of them paid for a state vector it threw away.
cProfile over 200 scalar
losscalls on an 8x8 matrix:86% of the runtime was the discarded
evolve, which is roughly 15x moreexpensive than the
CPUQVM.runthat actually produces the loss.It is not an infinite loop and not exponential blow-up. Removing the dead work
is enough.
Fix
Move the three
StateVectorlines inside theelsebranch. Nothing elsechanges: the probability path, the shot count and the normalization check in
parse_quantum_result_listare all untouched, so error behaviour such as thenanValueError on a zero matrix is preserved.Before / after
The optimizer trajectory is identical, same
nfev, samenit, same final loss,so this is runtime only:
example/QAlgBase/testeg_QSVD.pyon unmodifieddevelopvs with the fix:137.3 s → 24.7 s, 6 passed both times, and every printed singular value,
cosine similarity and error metric is byte-identical between the two runs.
CPU time drops further than wall time, 626 s → 24 s, because
evolvewasrunning multithreaded.
Correctness against
numpy.linalg.svd, seed 42, after the fix:u[:, 0]v[0]The residual error on the larger matrices is the variational ansatz hitting
SLSQP's default 100-iteration limit, which is pre-existing and unchanged by
this PR.
Tests
Four tests added to
test/QAlgBase/Test_QSVD_SVD.py:test_singular_values_against_numpy: 4x4, relative error againstnumpy.linalg.svdbelow 1e-2test_singular_vectors_against_numpy: 4x8, left and right dominant singularvectors overlap the numpy ones above 0.99, using the index
lossreportstest_loss_skips_state_vector_on_scalar_path: regression guard, countsStateVectorconstruction and asserts 0 on the scalar path, 1 on the matrixpath
test_qsvd_min_runtime: 8x8QSVD_minunder 20 sBoth regression tests fail on unmodified
develop(the runtime one at 30.8 s)and pass with the fix.
Checked and deliberately left out
max_eigderives both register indices frommax_index % 2**q0. The rightregister index is semantically
max_index // 2**q0, but the only indices themodule ever produces are the diagonal ones
d * 2**q0 + dwithd < 2**q0, forwhich the two expressions are equal. Verified on 4x4, 4x8, 8x4 and 2x4 with
non-zero argmax values (argmax 10, 15, 27):
lo == hievery time, cosineoverlap 1.0 either way. No observable defect, so no change.