feat(#162): hnsw_export halfvec 지원 — dim 상한 문제 해결 - #185
Merged
Conversation
동작 변경 없음(Tidy First 구조적 커밋) — 모든 호출부가 리터럴 4를 전달하므로 지금은 항상 fp32와 동일하게 동작한다. halfvec 배선은 다음 커밋에서. - elem_tuple_size(dim) -> elem_tuple_size(dim, bytes_per_dim) - write_elem_page()의 esize/vl_len 계산에 bytes_per_dim 추가. memcpy 자체는 아직 무조건 fp32(dim*sizeof(float)) — halfvec 분기는 다음 커밋. - 중복된 두 페이지-fit 체크(:1064-1077 근방, :1636-1648 근방)를 공유 헬퍼 check_hnsw_page_fit(dim, bytes_per_dim, m, maxlevel)로 추출. 에러 문구를 하나로 통일(bytes_per_dim 필드 추가) — 기존 문구를 참조하는 테스트 없음을 test/expected 전수 확인 후 진행. 검증(L40S, pg-cuvs-167-sidecar): installcheck 39/39, isolation 6/6, 배포는 preflight 6개 전부 통과 상태에서.
CAGRA GPU 빌드는 fp32 그대로 두고(cuVS는 fp16 CAGRA 미지원, ADR-054),
write_elem_page()의 마지막 memcpy에서만 halfvec으로 narrow-cast한다.
타겟 컬럼의 실제 카탈로그 타입(atttypid)으로 vector/halfvec을 판별해
bytes_per_dim을 fill_hnsw_from_hnswlib_impl/fill_hnsw_from_cagra_ipc_impl
양쪽에 관통시킨다. halfvec_{l2,ip,cosine}_ops 3종 opclass를 pg_cuvs_hnsw
AM에 등록(0.8.0, SQL-visible 신규 표면이라 마이너 범프). pgvector<0.7.0
+ halfvec 타겟 조합은 ERROR로 조기 차단(halfvec이 없는 버전이라 반드시
실패할 조합).
VM 검증: installcheck 39/39, isolation 6/6 GREEN. 수기 스모크(dim=8,
18벡터, mode=nsw/hnswlib 양쪽) — 검색 정합성(id=1 정확 매치), 재빌드
결정성(155648 bytes 동일) 확인.
기존 check_hnsw_page_fit()은 CAGRA 빌드 + hnswlib 파일 생성이 끝난 뒤에야 실행돼, 거부될 빌드에도 GPU 시간을 다 썼다. cuvs_hnsw_ambuild()와 pg_cuvs_build_hnsw() 양쪽에 동일한 체크를 GPU/IPC 작업 전으로 당겨 추가한다. maxlevel은 실제 실행 전에는 알 수 없으므로(코퍼스 크기 의존) 고정 safe_maxlevel=5로 다소 보수적으로 거부한다 — 이슈 본문의 방침과 일치. M은 소스 CAGRA의 graph_degree reloption(없으면 cuVS 기본값 64)에서 유도한다. 기존 사후 체크는 백스톱으로 남긴다. VM 검증: installcheck 39/39, isolation 6/6 GREEN. dim=1900/M=32/ maxlevel=5(상한 1678) 수동 케이스가 daemon 로그상 export_adjacency IPC 호출 전에 정확히 거부됨을 확인 — CAGRA 소스 자체 빌드는 별개로 필요하지만(소스 인덱스이므로), HNSW 변환 단계의 GPU 왕복은 스킵됐다.
…AM 경로 halfvec - test/sql/build_hnsw_halfvec.sql(신규): dim=8/18벡터 end-to-end(mode=nsw/ hnswlib), index_bytes 재빌드 결정성, dim=3072(이슈의 헤드라인 케이스 — vector HNSW 상한 2000으로는 도달 불가) 빌드+검색 정합성. - build_hnsw_edge.sql Case 6: dim-ceiling 사전 체크가 GPU 작업 전에 실제로 거부하는지 fp32(dim=1900/M=32)·halfvec(dim=3600/M=32) 양쪽 확인. - pg_cuvs_hnsw.sql: CREATE INDEX ... USING pg_cuvs_hnsw DDL 경로에 halfvec 타겟 케이스 1개 추가(기존 3종은 전부 vector). - Makefile REGRESS에 build_hnsw_halfvec 등록. 새 빌드 호출은 모두 client_min_messages=warning으로 감싸 import NOTICE의 카탈로그 OID가 expected 출력에 박히지 않도록 함(기존 pg_cuvs_hnsw.sql과 동일 패턴) — 처음 작성 시 이 억제가 빠져 OID가 그대로 노출되는 걸 발견, 플레이키해지기 전에 수정. VM 검증: installcheck 40/40, isolation 6/6 — 2회 연속 GREEN(안정성 확인).
같은 CAGRA 그래프에서 fp32/halfvec 두 HNSW를 export해 fp16 narrow-cast만 격리 측정. wiki_all 768d N=100K를 자기연결로 dim=1536/3072 확장(√m 재정규화 — 순위 보존, 새 GT 계산 불필요, API 비용 0). dim=1536: recall delta +0.0002(ef=40/100 동일, 2회 재현) — fp16 비용 사실상 무시할 수준. dim=3072: fp32 export 자체가 check_hnsw_page_fit()에서 거부됨 (needed=13736 > 8160) — halfvec만이 유일한 경로이며 recall 0.987~0.998로 정상 동작. #162가 존재하는 이유 자체가 실측으로 확인됨. 정직성 caveat: 자기연결 데이터는 실제 고차원 임베딩보다 판별 정보가 적어 절대 recall 수치는 "실제 1536/3072d recall" 주장이 아님 — paired delta와 dim=3072 buildability 사실만이 유효한 결론.
적대적 코드 리뷰(HIGH 2건, MEDIUM 2건, LOW 다수)에서 발견된 실제 결함 수정: - [HIGH] safe_maxlevel=5 사전 체크가 mode 무관하게 적용돼 mode='nsw'(기본값, 실제 max_level 항상 0)의 dim 1679~1918 구간 fp32 빌드를 회귀시켰다 — 기존에 되던 REINDEX/pg_dump 복원까지 새 .so 로드 순간 깨지는 상태였다. 이제 mode='nsw'는 정확한 maxlevel=0을, 그 외 모드는 기존 보수적 5를 쓴다. build_hnsw_edge.sql의 Case 6(거부 경계)을 실제 상한 밖 dim으로 올리고, Case 7로 회귀 자체를 막는 가드(dim=1900 fp32 mode='nsw' 성공 확인) 추가. - [HIGH] bytes_per_dim_for_typid()가 TypenameGetTypid()로 이름→OID를 캐시했는데, 이 함수는 호출자의 search_path에 의존한다(PG 소스 확인) — 제한된 search_path(SECURITY DEFINER, 스키마 한정 설치)에서 평범한 fp32 빌드까지 스퓨리어스 ERROR가 날 수 있었다. OID→이름 역방향 조회(syscache TYPEOID 직접 조회)로 교체 — search_path 의존성 완전 제거, 캐시도 불필요. - [MEDIUM] pg_cuvs_build_hnsw()의 사전 체크가 cagra AM 검증보다 먼저 실행돼 cagra가 아닌 인덱스를 넘기면 다른 AM의 옵션 구조체를 CuvsCagraOptions로 오독할 수 있었다 — 블록 안에 명시적 relam 체크 추가. - [MEDIUM] 0.8.0 SQL이 halfvec opclass를 무조건 설치해 pgvector<0.7.0에서 CREATE/ALTER EXTENSION 자체가 실패(fp32 전용 사용자도 영향) — 이미 도달 불가능해진 require_halfvec_capable_pgvector() C 가드는 삭제하고, README 최소 요구사항을 0.7.0+로 명시. - [LOW] check_hnsw_page_fit()에 dim<=0 가드 추가(부호 없는 랩어라운드로 체크가 무력화되는 경로 차단). BENCHMARK.md §2.1e의 미검증 "두 번 재현" 문구와 stale된 사전 체크 에러 수치를 mode-aware 수정 반영 재측정치로 갱신. pg_cuvs_hnsw.sql의 잘못된 상호참조 주석 수정. VM 재검증: installcheck 40/40, isolation 6/6 GREEN. dim=1900 fp32 mode='nsw'가 이제 실제로 성공 빌드됨을 확인(회귀 수정 증거).
Collaborator
Author
|
적대적 코드 리뷰(에이전트) 결과 및 대응 — 마지막 커밋( HIGH — 실제 회귀, 수정함
MEDIUM — 수정함
LOW — 수정함
검증 안 됐다고 확인된 것들 (문제 없음)
VM 재검증: |
두 CI 실패 원인: 1. docs-contract-check: src/cuvs_version.h의 PG_CUVS_VERSION이 0.7.0에 머물러 있었다 — 0.8.0 버전 범프 시 이 SSOT를 빠뜨렸다. control/Makefile은 갱신했지만 GCS manifest 버전 스탬프용 헤더는 놓침. 함께 docs-contract-check.sh 자체의 하드코딩된 "현재 버전은 0.7.0" 계약과 release-upgrade.md/ rollback-and-cleanup.md의 "현재 버전" 실측 예시 출력을 0.8.0으로 갱신 (이 문서들은 append-only 이력이 아니라 현재 상태를 반영해야 하는 운영 플레이북 — doc-map.md 기준). 2. Tier-1 CPU shim installcheck: 신규 build_hnsw_halfvec.sql이 REGRESS_TIER2_ONLY에 빠져 CPU shim(GPU 없음)에서 실행되다 실패했다. build_hnsw/build_hnsw_edge/pg_cuvs_hnsw와 동일하게 CAGRA→HNSW GPU 그래프 export를 쓰는 테스트라 원래 Tier-2 전용이어야 했다 — 목록에 추가. 로컬 검증: scripts/docs-contract-check.sh 통과 확인.
ph_hnsw(vector)/ph_hnsw_solo(vector)는 REINDEX를 명시적으로 테스트했는데 ph_hnsw_hv(halfvec 표현식 인덱스)는 DROP만 하고 REINDEX 경로가 비어 있었다. 표현식 인덱스는 attnum=1의 atttypid가 표현식의 결과 타입(halfvec)으로 카탈로그에 고정되고 REINDEX로도 바뀌지 않으므로 동작해야 한다는 추론은 있었지만 실측이 없었다 — 지금 추가해 확인(REINDEX 후에도 top-1=id=20 정확). VM 검증: installcheck 40/40, isolation 6/6 GREEN.
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.
Summary
write_elem_page()의 마지막 memcpy에서만 halfvec으로 narrow-cast —float_to_half()는 손수 구현한 IEEE-754 round-to-nearest-even이며 라이브 pgvector 0.8.6과 335/335 비트 단위 일치 검증(round-to-even tie 케이스 포함).halfvec_{l2,ip,cosine}_ops3종 opclass를pg_cuvs_hnswAM에 등록(0.8.0, SQL-visible 신규 표면이라 마이너 범프).check_hnsw_page_fit)를 GPU 작업 전으로 당겨 고정safe_maxlevel=5로 조기 거부 — 기존엔 CAGRA 빌드+hnswlib 파일 생성이 끝난 뒤에야 거부돼 GPU 시간을 낭비했다.Commits (Tidy First: 구조 → 동작 → 동작 → 테스트)
refactor:elem_tuple_size/write_elem_page를bytes_per_dim으로 파라미터화 (동작 불변)feat: halfvec 쓰기 경로 + 카탈로그 타입 판별 + opclass SQL + 버전 핀 (0.8.0)feat: dim 상한 체크를 GPU 작업 전으로 이동test: halfvec 커버리지 — 신규build_hnsw_halfvec.sql, dim 상한 거부 케이스, AM 경로 halfvec 케이스Test plan
make gpu-deploy→make gpu-verify-deployed(6항목 전부 GREEN)make installcheck40/40 GREEN, 2회 연속 재현으로 안정성 확인(신규 테스트에 OID 플레이키니스 없음)make installcheck-isolation6/6 GREENfloat_to_half()335/335 라이브 pgvector 0.8.6 대비 비트 단위 일치 (round-to-even tie 케이스 포함)wiki_all_1Mself-concatenation 재사용) — 별도 후속 커밋으로 진행 예정