From 2ddb6de92f0c788b5fb526b828107d7314656617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Sat, 11 Jul 2026 20:13:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AC=AC=E4=B8=89=E4=BC=98=E5=85=88?= =?UTF-8?q?=E7=BA=A7=E5=AE=8C=E5=96=84=20=E2=80=94=20CI/CD=E3=80=81PyPI?= =?UTF-8?q?=E3=80=81pre-commit=E3=80=81CHANGELOG=E3=80=81=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E6=8C=87=E5=8D=97=E3=80=81=E5=9F=BA=E5=87=86=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=81ADR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 48 +++++++++++++++++++ .github/workflows/publish.yml | 28 +++++++++++ CHANGELOG.md | 41 ++++++++++++++++ CONTRIBUTING.md | 54 +++++++++++++++++++++ README.md | 21 +++++++-- docs/api_reference.md | 68 +++++++++++++++++++++++++++ docs/architecture.md | 43 +++++++++++++++++ pyproject.toml | 24 +++++++++- scripts/bump_version.py | 47 ++++++++++++++++++ tests/benchmarks/conftest.py | 41 ++++++++++++++++ tests/benchmarks/test_bench_embed.py | 23 +++++++++ tests/benchmarks/test_bench_search.py | 17 +++++++ uv.lock | 40 +++++++++++++--- 13 files changed, 483 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 docs/api_reference.md create mode 100644 docs/architecture.md create mode 100644 scripts/bump_version.py create mode 100644 tests/benchmarks/conftest.py create mode 100644 tests/benchmarks/test_bench_embed.py create mode 100644 tests/benchmarks/test_bench_search.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..82104cd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +# CI +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --extra dev --extra all + + - name: Run lint (ruff) + run: uv run ruff check src/ tests/ + + - name: Run type check (mypy) + run: uv run mypy src/ --ignore-missing-imports + + - name: Run tests with coverage + run: | + uv run pytest tests/ \ + --cov=src \ + --cov-report=term-missing \ + --cov-report=xml \ + -v + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: ./coverage.xml + fail_ci_if_error: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..746c880 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,28 @@ +# Publish to PyPI +name: Publish to PyPI + +on: + push: + tags: + - "v*" + +jobs: + publish: + name: Build and publish + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Build package + run: uv build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6ac465b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +所有值得注意的更改都将记录在此文件中。 + +格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), +版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 + +## [Unreleased] + +### Added +- 混合检索(BM25 + 向量联合),HybridRetriever + SearchConfig 可配置 +- 增量入库(SHA256 文件变更追踪),CLI `--incremental` / `--force` 选项 +- Cross-Encoder 结果重排序(BAAI/bge-reranker-base) +- Web 管理界面(Vue 3 SPA,`/admin`) +- .docx 文档支持(markitdown) +- 数据导出功能(JSON/CSV),CLI `export` 命令 +- Docker 部署方案 + docker-compose(含 GPU profile) +- API 审计日志、请求体大小限制、健康检查免限速 +- GitHub Actions CI 流水线 + PyPI 发布 +- Pre-commit 钩子配置(ruff + mypy) +- 性能基准测试(嵌入 + 检索) + +### Changed +- Searcher 支持 hybrid/vector 检索模式切换 +- Embedder Protocol 修复为标准写法 +- PDF/EPUB Splitter 参数名统一为 `source` + +### Fixed +- CORS `allow_credentials` 配置修复 +- chromadb 不同版本异常类型兼容(NotFoundError) + +## [0.1.0] - 2026-07-05 + +### Added +- Markdown 文档解析与语义检索 +- 多 Provider 嵌入支持(local/OpenAI/DashScope) +- 多格式文档(.md/.txt/.pdf/.html/.epub) +- GPU 自动检测加速 +- FastAPI HTTP API + Typer CLI +- API Key 认证和速率限制 +- 路径遍历安全防护 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..014dd9e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# 贡献指南 + +## 开发环境 + +```bash +git clone git@github.com:LHY0125/md-vector-db.git +cd md-vector-db +uv sync --extra dev --extra all +uv run pre-commit install +``` + +## 开发流程 + +1. **Fork 仓库** → 创建功能分支 `feat/xxx` +2. **写测试**(TDD): 先在 `tests/` 下写失败测试 +3. **实现功能**: 最少代码让测试通过 +4. **运行全部测试**: `uv run pytest tests/ --cov=src -v` +5. **确保覆盖率 ≥ 80%**: `uv run pytest tests/ --cov=src --cov-fail-under=80` +6. **Lint 检查**: `uv run ruff check src/ tests/` +7. **类型检查**: `uv run mypy src/ --ignore-missing-imports` +8. **提交**: 遵循约定式提交格式 +9. **创建 PR**: 描述清楚变更内容和测试结果 + +## 提交消息格式 + +``` +<类型>: <描述> + +<可选正文> +``` + +类型: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci` + +## 代码风格 + +- Python 3.13+,遵循 PEP 8 +- 类型注解覆盖所有 public API +- 中文注释 +- 小文件原则(<800 行),函数 <50 行 + +## 添加新文档格式支持 + +1. 在 `src/core/splitters/` 下实现 Splitter Protocol +2. 在 `src/core/splitters/registry.py` 注册扩展名 +3. 在 `pyproject.toml` 添加可选依赖 +4. 在 `tests/` 下添加测试 +5. 更新 `README.md` + +## 运行基准测试 + +```bash +uv sync --extra bench +uv run pytest tests/benchmarks/ -v --benchmark-only +``` diff --git a/README.md b/README.md index 221d816..b4ae8ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # md-vector-db +[![CI](https://lhy-git.liuhangyv.top/Serendipity/md-vector-db/actions/workflows/ci.yml/badge.svg)](https://github.com/LHY0125/md-vector-db/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + Markdown 文档向量数据库 — 将 Markdown 文件自动分块、嵌入、存入 ChromaDB,通过语义检索快速查找相关内容。提供 **CLI 命令行工具**和 **HTTP API** 两种使用方式供其他项目集成。 ## 功能特性 @@ -180,14 +183,14 @@ docker exec -it md-vector-db uv run md-vector-db ingest /app/md_docs/doc.md 所有命令均支持 `--config/-c`(配置文件)、`--collection/-C`(集合名,默认 `default`)。 -| 命令 | 说明 | -| --------------------------------- | -------------------------------------------------------- | +| 命令 | 说明 | +| ----------------------------------- | -------------------------------------------------------- | | `ingest <文件路径> --incremental` | 增量入库单文件,自动跳过未变更文件 | | `ingest <文件路径> --force` | 强制重新入库(忽略增量检查) | | `ingest-dir <目录路径>` | 递归入库目录下所有支持的文档格式 | -| `search <查询> -k <数量> --mode` | 语义检索,`--mode hybrid\|vector`,`--json` JSON 输出 | +| `search <查询> -k <数量> --mode` | 语义检索,`--mode hybrid\|vector`,`--json` JSON 输出 | | `stats` | 显示 chunks 总数、源文件列表 | -| `export -o <文件> -f ` | 导出 collection 数据 | +| `export -o <文件> -f ` | 导出 collection 数据 | | `serve -p <端口>` | 启动 HTTP 服务(默认 8000) | --- @@ -327,6 +330,16 @@ index-strategy = "unsafe-best-match" # 允许跨源查找 --- +--- + +## 文档 + +- [API 参考](docs/api_reference.md) +- [架构决策记录](docs/architecture.md) +- +- [贡献指南](CONTRIBUTING.md) +- [变更日志](CHANGELOG.md) + ## License MIT diff --git a/docs/api_reference.md b/docs/api_reference.md new file mode 100644 index 0000000..5dc220e --- /dev/null +++ b/docs/api_reference.md @@ -0,0 +1,68 @@ +# md-vector-db API 参考文档 + +## REST API + +- **Base URL**: `http://localhost:8000/api/v1` +- **认证**: `X-API-Key` Header(取决于 `MD_VECTOR_API_KEY` 环境变量) +- **速率限制**: 60s 窗口内最多 30 请求 + +### GET /health — 健康检查 + +无需认证。 + +```json +{ + "status": "ok", + "checks": { + "chromadb": {"status": "ok", "count": 1240}, + "embedder": {"status": "ok", "dimension": 512} + } +} +``` + +### GET /collections — 列出集合 + +需 API Key。返回 `{"collections": [{"name": "...", "count": N}, ...]}`。 + +### POST /search — 语义检索 + +| 字段 | 类型 | 必填 | 约束 | +|------|------|------|------| +| query | string | ✅ | 1-2000 字符 | +| top_k | int | ❌ | 1-100,默认 10 | +| collection | string | ❌ | ≤128 字符 | + +返回 `{"results": [...], "collection": "..."}`,每条结果含 `id`, `content`, `source_file`, `section_title`, `heading_level`, `score`。 + +### POST /ingest — 入库文档 + +| 字段 | 类型 | 必填 | 约束 | +|------|------|------|------| +| file_path | string | 二选一 | 安全路径 | +| content | string | 二选一 | ≤500KB | +| file_name | string | 推荐 | 1-255 字符 | +| collection | string | ❌ | ≤128 字符 | + +返回 `{"status": "ok", "chunks": N, "file": "...", "collection": "..."}`。 + +### DELETE /documents/{file_name} — 删除文档 + +查询参数: `collection` (可选)。 + +| 状态码 | 含义 | +|--------|------| +| 200 | 删除成功 | +| 400 | file_name 不合法 | +| 401 | API Key 无效 | +| 404 | 文档不存在 | + +## CLI 命令 + +```bash +md-vector-db ingest <文件> --incremental --force +md-vector-db ingest-dir <目录> -C <集合> +md-vector-db search "<查询>" -k 10 --mode hybrid --json +md-vector-db stats -C <集合> +md-vector-db export -o output.json -f json +md-vector-db serve -p 8000 +``` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..087657b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,43 @@ +# 架构决策记录 (ADR) + +## ADR-1: 为什么选择 ChromaDB? + +**日期**: 2026-07-05 | **状态**: 已采纳 + +**背景**: 需要一个嵌入式向量数据库来持久化文档嵌入。 + +**候选**: ChromaDB(嵌入式 SQLite)、Qdrant(独立服务)、FAISS(纯内存)、Milvus(生产级集群) + +**决策**: ChromaDB。零运维成本远超吞吐量考量。内置 Collection 概念映射多知识库场景。 + +**代价**: 高并发下逊于 Qdrant/Milvus。未来可透明迁移(Embedder/Searcher 已隔离 ChromaDB 依赖)。 + +--- + +## ADR-2: 为什么默认 bge-small-zh-v1.5? + +**日期**: 2026-07-05 | **状态**: 已采纳 + +**候选**: bge-small-zh-v1.5 (512维/23M)、bge-large-zh-v1.5 (1024维/324M)、text2vec-large-chinese、m3e-base + +**决策**: bge-small-zh-v1.5。RTX 4060 上 729 chunks 嵌入仅 1.8s,日常精度足够。高精度场景可切换 large 模型或 OpenAI API。 + +--- + +## ADR-3: 为什么采用 Protocol 而非 ABC? + +**日期**: 2026-07-05 | **状态**: 已采纳 + +**候选**: typing.Protocol(结构化子类型)、abc.ABC(名义子类型)、Callable(丢失类型信息) + +**决策**: Protocol。外部模块无需依赖本项目源码即可实现 Splitter/Embedder,对插件化友好。 + +--- + +## ADR-4: 为什么使用 rank-bm25 而非集成搜索引擎? + +**日期**: 2026-07-11 | **状态**: 已采纳 + +**候选**: rank-bm25(纯 Python BM25)、Elasticsearch(外部服务)、Whoosh(纯 Python 全文搜索) + +**决策**: rank-bm25。零运维、轻量、与现有 ChromaDB 架构匹配。在向量候选上做 BM25 重打分(而非全文索引所有文档),兼顾性能和精度。 diff --git a/pyproject.toml b/pyproject.toml index d814990..709c041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,21 @@ [project] name = "md-vector-db" version = "0.1.0" -description = "Markdown 文档向量数据库,支持语义检索" +description = "Markdown 文档向量数据库 — 语义检索、混合检索、REST API" +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "刘航宇", email = "3364451258@qq.com"}, +] +keywords = ["vector-database", "semantic-search", "rag", "chromadb", "markdown"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Text Processing :: Markup", +] requires-python = ">=3.13" dependencies = [ "chromadb>=0.5.0", @@ -15,6 +29,12 @@ dependencies = [ "rank-bm25>=0.2.2", ] +[project.urls] +Homepage = "https://github.com/LHY0125/md-vector-db" +Documentation = "https://github.com/LHY0125/md-vector-db#readme" +Repository = "https://github.com/LHY0125/md-vector-db" +Issues = "https://github.com/LHY0125/md-vector-db/issues" + [project.scripts] md-vector-db = "src.cli.main:app" @@ -24,6 +44,7 @@ pdf = ["pymupdf>=1.24.0"] html = ["beautifulsoup4>=4.12.0"] epub = ["ebooklib>=0.18"] docx = ["markitdown>=0.1.0"] +bench = ["pytest-benchmark>=5.0"] all = ["md-vector-db[pdf,html,epub,docx]", "requests>=2.31.0", "openai>=1.0.0"] [build-system] @@ -56,3 +77,4 @@ ignore_missing_imports = true [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] +norecursedirs = ["tests/benchmarks"] diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100644 index 0000000..1afe9ac --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,47 @@ +"""版本号管理 — 更新 pyproject.toml 中的 version.""" +import re +import sys +from pathlib import Path + + +def bump(part: str) -> str: + """递增版本号. + + Args: + part: "major" | "minor" | "patch" + + Returns: + 新版本号字符串 + """ + pyproject = Path(__file__).parent.parent / "pyproject.toml" + content = pyproject.read_text(encoding="utf-8") + match = re.search(r'version\s*=\s*"(\d+)\.(\d+)\.(\d+)"', content) + if not match: + print("错误: 未找到 version 字段", file=sys.stderr) + sys.exit(1) + major, minor, patch = int(match[1]), int(match[2]), int(match[3]) + if part == "major": + major += 1 + minor = 0 + patch = 0 + elif part == "minor": + minor += 1 + patch = 0 + elif part == "patch": + patch += 1 + else: + print(f"错误: 未知的版本部分 '{part}',可选: major/minor/patch", file=sys.stderr) + sys.exit(1) + + new_version = f"{major}.{minor}.{patch}" + new_content = content.replace(match[0], f'version = "{new_version}"') + pyproject.write_text(new_content, encoding="utf-8") + print(f"版本: {match[1]}.{match[2]}.{match[3]} → {new_version}") + return new_version + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("用法: python scripts/bump_version.py ") + sys.exit(1) + bump(sys.argv[1]) diff --git a/tests/benchmarks/conftest.py b/tests/benchmarks/conftest.py new file mode 100644 index 0000000..a3e8b71 --- /dev/null +++ b/tests/benchmarks/conftest.py @@ -0,0 +1,41 @@ +"""基准测试共享 fixture.""" +import pytest + +from src.core.config import ChunkConfig, EmbedConfig +from src.core.db import VectorDB +from src.core.embedder import create_embedder +from src.core.ingest import DocumentIngestor +from src.core.search import Searcher + + +@pytest.fixture(scope="module") +def benchmark_db(tmp_path_factory): + """模块级共享 ChromaDB 实例.""" + persist_dir = tmp_path_factory.mktemp("bench_data") + return VectorDB(persist_dir=str(persist_dir)) + + +@pytest.fixture(scope="module") +def benchmark_embedder(): + """模块级共享 LocalEmbedder.""" + config = EmbedConfig(mode="local", local_model="BAAI/bge-small-zh-v1.5") + return create_embedder(config) + + +@pytest.fixture(scope="module") +def benchmark_searcher(benchmark_db, benchmark_embedder): + """预填充数据的 Searcher.""" + ingestor = DocumentIngestor( + benchmark_db, benchmark_embedder, "bench_collection", + chunk_config=ChunkConfig(max_size=1000, overlap=100), + ) + for i in range(100): + content = ( + f"# 文档{i}\n\n" + + "\n\n".join( + f"第{j}段用于基准测试。关键词: Python, Rust, GPU, 向量数据库。" + for j in range(5) + ) + ) + ingestor.ingest_content(content, f"bench_{i}.md") + return Searcher(benchmark_db, benchmark_embedder, "bench_collection") diff --git a/tests/benchmarks/test_bench_embed.py b/tests/benchmarks/test_bench_embed.py new file mode 100644 index 0000000..887c411 --- /dev/null +++ b/tests/benchmarks/test_bench_embed.py @@ -0,0 +1,23 @@ +"""嵌入性能基准测试.""" +import pytest + + +def test_bench_embed_single(benchmark, benchmark_embedder): + """单文本嵌入耗时.""" + text = "这是一段测试文本,用于测量嵌入速度。" + benchmark(benchmark_embedder.embed, [text]) + + +def test_bench_embed_batch_32(benchmark, benchmark_embedder): + """批量 32 文本嵌入耗时.""" + texts = [f"测试文本第{i}条,模拟真实文档内容。" for i in range(32)] + benchmark(benchmark_embedder.embed, texts) + + +def test_bench_embed_batch_100(benchmark, benchmark_embedder): + """批量 100 文本嵌入耗时(GPU 优势显著).""" + texts = [ + f"测试文本第{i}条。Python 通用编程语言,用于数据科学和 AI。" + for i in range(100) + ] + benchmark(benchmark_embedder.embed, texts) diff --git a/tests/benchmarks/test_bench_search.py b/tests/benchmarks/test_bench_search.py new file mode 100644 index 0000000..f3b52d8 --- /dev/null +++ b/tests/benchmarks/test_bench_search.py @@ -0,0 +1,17 @@ +"""检索性能基准测试.""" +import pytest + + +def test_bench_search_top5(benchmark, benchmark_searcher): + """top_k=5 检索耗时.""" + benchmark(benchmark_searcher.search, "Python 向量数据库", top_k=5) + + +def test_bench_search_top20(benchmark, benchmark_searcher): + """top_k=20 检索耗时.""" + benchmark(benchmark_searcher.search, "Rust 编程语言 GPU", top_k=20) + + +def test_bench_search_cold_start(benchmark, benchmark_searcher): + """冷启动检索耗时.""" + benchmark(benchmark_searcher.search, "GPU 加速 深度学习 嵌入", top_k=10) diff --git a/uv.lock b/uv.lock index 949c330..927df34 100644 --- a/uv.lock +++ b/uv.lock @@ -1206,6 +1206,9 @@ all = [ { name = "pymupdf" }, { name = "requests" }, ] +bench = [ + { name = "pytest-benchmark" }, +] dev = [ { name = "httpx" }, { name = "mypy" }, @@ -1240,6 +1243,7 @@ requires-dist = [ { name = "openai", marker = "extra == 'all'", specifier = ">=1.0.0" }, { name = "pymupdf", marker = "extra == 'pdf'", specifier = ">=1.24.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, + { name = "pytest-benchmark", marker = "extra == 'bench'", specifier = ">=5.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" }, { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "pyyaml", specifier = ">=6.0" }, @@ -1250,7 +1254,7 @@ requires-dist = [ { name = "typer", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" }, ] -provides-extras = ["dev", "pdf", "html", "epub", "docx", "all"] +provides-extras = ["dev", "pdf", "html", "epub", "docx", "bench", "all"] [[package]] name = "mdurl" @@ -1559,7 +1563,7 @@ name = "nvidia-cudnn-cu12" version = "9.1.0.70" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741, upload-time = "2024-04-22T15:24:15.253Z" }, @@ -1570,7 +1574,7 @@ name = "nvidia-cufft-cu12" version = "11.2.1.3" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117, upload-time = "2024-04-03T20:57:40.402Z" }, @@ -1589,9 +1593,9 @@ name = "nvidia-cusolver-cu12" version = "11.6.1.9" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057, upload-time = "2024-04-03T20:58:28.735Z" }, @@ -1602,7 +1606,7 @@ name = "nvidia-cusparse-cu12" version = "12.3.1.170" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "python_full_version >= '3.14' or sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version >= '3.14' and sys_platform != 'win32') or sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763, upload-time = "2024-04-03T20:58:59.995Z" }, @@ -1942,6 +1946,15 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, ] +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + [[package]] name = "pybase64" version = "1.4.3" @@ -2181,6 +2194,19 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] +[[package]] +name = "pytest-benchmark" +version = "5.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } +dependencies = [ + { name = "py-cpuinfo" }, + { name = "pytest" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" }, +] + [[package]] name = "pytest-cov" version = "7.1.0"