From 74cca19021605ac3bc84f175293768d0b09ec7b4 Mon Sep 17 00:00:00 2001 From: tangwang Date: Mon, 29 Dec 2025 23:22:14 +0800 Subject: [PATCH] cnclip --- docs/CLIP_REPLICAS_GUIDE.md | 415 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- docs/CNCLIP_SERVICE.md | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------- docs/GIT_SUBMODULE_GUIDE.md | 323 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- examples/clip_rest_api.py | 254 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- examples/simple_examples.py | 93 --------------------------------------------------------------------------------------------- examples/test_cnclip_example.py | 177 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- examples/test_curl_examples.sh | 159 --------------------------------------------------------------------------------------------------------------------------------------------------------------- scripts/compare_index_mappings.py | 189 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- scripts/remove_clip_submodule.sh | 210 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ scripts/start_clip_api.sh | 154 ---------------------------------------------------------------------------------------------------------------------------------------------------------- scripts/start_cnclip_service.sh | 1 + scripts/stop_clip_api.sh | 87 --------------------------------------------------------------------------------------- scripts/stop_cnclip_service.sh | 43 ++++++++++++++++++++++++++++++++----------- scripts/test_cnclip_client.py | 109 ------------------------------------------------------------------------------------------------------------- scripts/test_cnclip_service.py | 320 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 15 files changed, 147 insertions(+), 2628 deletions(-) delete mode 100644 docs/CLIP_REPLICAS_GUIDE.md delete mode 100644 docs/GIT_SUBMODULE_GUIDE.md delete mode 100644 examples/clip_rest_api.py delete mode 100644 examples/simple_examples.py delete mode 100644 examples/test_cnclip_example.py delete mode 100755 examples/test_curl_examples.sh delete mode 100644 scripts/compare_index_mappings.py delete mode 100755 scripts/remove_clip_submodule.sh delete mode 100755 scripts/start_clip_api.sh delete mode 100755 scripts/stop_clip_api.sh delete mode 100755 scripts/test_cnclip_client.py delete mode 100755 scripts/test_cnclip_service.py diff --git a/docs/CLIP_REPLICAS_GUIDE.md b/docs/CLIP_REPLICAS_GUIDE.md deleted file mode 100644 index 9935559..0000000 --- a/docs/CLIP_REPLICAS_GUIDE.md +++ /dev/null @@ -1,415 +0,0 @@ -# CN-CLIP 服务副本数配置指南 - -## 📊 副本数(Replicas)详解 - -### 什么是副本? - -**副本(Replicas)** 是指同时运行的模型实例数量。每个副本都是独立的进程,可以: -- 并行处理多个请求 -- 提高服务吞吐量 -- 充分利用多核 CPU 或多 GPU - -**关键点**: -- ✅ 每个副本加载一份完整的模型到内存/显存 -- ✅ 副本间共享网络端口(通过负载均衡) -- ✅ 多副本**不会**自动分配到不同 GPU(需手动配置) - ---- - -## 💾 显存占用分析 - -### 问题:2个副本会比1个副本多一倍显存吗? - -**答案:是的!基本成倍增加。** - -| 副本数 | 模型显存 | 峰值显存 | 总显存占用 | -|--------|---------|---------|-----------| -| 1副本 | ~2-3 GB | ~4-5 GB | **~4-5 GB** | -| 2副本 | ~4-6 GB | ~8-10 GB | **~8-10 GB** | -| 3副本 | ~6-9 GB | ~12-15 GB | **~12-15 GB** | -| 4副本 | ~8-12 GB | ~16-20 GB | **~16-20 GB** | - -**说明**: -- **模型显存**:模型权重(固定部分) -- **峰值显存**:模型 + 激活值 + 中间结果(批处理时) -- **CN-CLIP/ViT-H-14**:约 2-3 GB 模型权重 - -### 你的 GPU 情况 - -``` -GPU: Tesla T4 -总显存: 16384 MiB (16 GB) -当前空闲: ~2 GB -``` - -**推荐配置**: -- **1副本**:最安全,适合当前场景 -- **2副本**:需要先停止其他占用显存的程序 -- **3+副本**:不建议(显存不足) - ---- - -## ⚙️ 配置副本数 - -### 方法 1:修改配置文件(推荐) - -编辑 `third-party/clip-as-service/server/clip_server/torch-flow.yml`: - -```yaml -jtype: Flow -version: '1' -with: - port: 51000 -executors: - - name: clip_t - uses: - jtype: CLIPEncoder - with: - name: 'CN-CLIP/ViT-H-14' - device: 'cuda' # 设备 - minibatch_size: 32 # 批处理大小 - num_worker_preprocess: 4 # 预处理线程 - dtype: 'float16' # 数据类型 - metas: - py_modules: - - clip_server.executors.clip_torch - timeout_ready: 3000000 - replicas: 2 # ← 修改这里(1, 2, 3...) -``` - -### 方法 2:命令行参数 - -```bash -# 启动时指定副本数(需要修改启动脚本) -python -m clip_server \ - --name 'CN-CLIP/ViT-H-14' \ - --replicas 2 \ - --port 51000 -``` - -### 方法 3:使用改进的启动脚本 - -我会在下面提供一个支持副本数的启动脚本。 - ---- - -## 📈 性能对比 - -### 吞吐量测试(理论值) - -**配置**:CN-CLIP/ViT-H-14, Tesla T4, batch_size=32 - -| 副本数 | QPS (请求/秒) | 显存占用 | 推荐场景 | -|--------|--------------|---------|---------| -| 1 | ~80-100 | ~4-5 GB | 开发测试、低并发 | -| 2 | ~150-180 | ~8-10 GB | 生产环境、中等并发 | -| 3 | ~220-260 | ~12-15 GB | 高并发(需24GB+显存)| -| 4 | ~300-350 | ~16-20 GB | 极高并发(需A100等)| - -**注意**: -- 实际性能取决于批处理大小、请求类型(文本/图像) -- 并发请求少时,多副本提升不明显 -- 并发请求多时,多副本显著提升吞吐量 - -### 延迟对比 - -| 副本数 | 单请求延迟 | P99 延迟 | 说明 | -|--------|-----------|---------|------| -| 1 | ~50ms | ~200ms | 无等待,直接处理 | -| 2 | ~50ms | ~150ms | 有等待,但处理快 | -| 4 | ~50ms | ~100ms | 等待少,处理很快 | - -**结论**: -- ✅ 多副本**不降低**单请求延迟 -- ✅ 多副本**显著降低** P99 延迟(高并发时) -- ✅ 多副本**显著提升**吞吐量 - ---- - -## 🎯 配置建议 - -### 场景 1:开发/测试(推荐) - -```yaml -replicas: 1 -minibatch_size: 32 -device: cuda # 或 cpu -``` - -**特点**: -- ✅ 显存占用低(~5GB) -- ✅ 足够测试使用 -- ✅ 启动快 - -### 场景 2:生产环境(中等并发) - -```yaml -replicas: 2 -minibatch_size: 32 -device: cuda -``` - -**前提条件**: -- ✅ GPU 显存 ≥ 12GB -- ✅ 没有其他显存占用程序 -- ✅ 需要 ~10GB 显存 - -**特点**: -- ✅ 吞吐量翻倍(~150-180 QPS) -- ✅ 处理并发请求能力强 -- ⚠️ 需要确保显存充足 - -### 场景 3:生产环境(高并发) - -```yaml -replicas: 2 -minibatch_size: 64 -device: cuda -``` - -**优化点**: -- ✅ 增大批处理(利用多副本优势) -- ✅ 吞吐量进一步提升 -- ⚠️ 需要更多显存(~12-15GB) - -### 场景 4:多 GPU 服务器 - -如果你有多块 GPU: - -```yaml -executors: - - name: clip_t_gpu0 - uses: - jtype: CLIPEncoder - with: - device: 'cuda:0' - replicas: 2 - uses: - jtype: CLIPEncoder - with: - device: 'cuda:1' - replicas: 2 -``` - -**说明**: -- 2块GPU,每块2个副本 = 共4个副本 -- 总吞吐量 ~300+ QPS -- 每块GPU占用 ~8-10GB - ---- - -## 🔍 监控和调试 - -### 查看显存占用 - -```bash -# 实时监控 -watch -n 1 nvidia-smi - -# 查看详细信息 -nvidia-smi --query-gpu=timestamp,name,memory.used,memory.free,utilization.gpu --format=csv - -# 持续监控(每秒刷新,共100次) -nvidia-smi dmon -s u -c 100 -``` - -### 查看副本进程 - -```bash -# 查看所有 clip_server 进程 -ps aux | grep clip_server - -# 应该看到: -# root 12345 clip_server (main) -# root 12346 clip_server (replica 1) -# root 12347 clip_server (replica 2) -# ... - -# 查看进程数 -ps aux | grep clip_server | wc -l - -# 应该 = replicas + 1 -``` - -### 测试吞吐量 - -```bash -# 使用测试脚本 -python scripts/test_cnclip_service.py --batch-size 100 - -# 观察日志 -tail -f logs/cnclip_service.log | grep "encoded" -``` - ---- - -## ⚠️ 常见问题 - -### Q1: 设置2副本后显存不足 (OOM) - -**错误信息**: -``` -RuntimeError: CUDA out of memory. Tried to allocate XXX MiB -``` - -**解决方案**: - -**方案 A**:减小批处理大小 -```yaml -replicas: 2 -minibatch_size: 16 # 从32减到16 -``` - -**方案 B**:使用更小的模型 -```yaml -name: 'CN-CLIP/ViT-L-14' # 从 H-14 改为 L-14 -replicas: 2 -``` - -**方案 C**:减少副本数 -```yaml -replicas: 1 # 回退到单副本 -``` - -### Q2: 多副本反而性能下降 - -**原因**: -- GPU 资源竞争(显存带宽、计算单元) -- 批处理太小(未充分利用并行) -- CPU 预处理成为瓶颈 - -**解决方案**: -```yaml -# 增大批处理 -minibatch_size: 64 - -# 增加预处理线程 -num_worker_preprocess: 8 - -# 或减少副本 -replicas: 1 -``` - -### Q3: 如何知道最佳副本数? - -**实验方法**: - -```bash -# 测试脚本 -for replicas in 1 2 3; do - echo "Testing $replicas replicas..." - - # 修改配置 - sed -i "s/replicas: .*/replicas: $replicas/" torch-flow.yml - - # 重启服务 - ./scripts/stop_cnclip_service.sh - ./scripts/start_cnclip_service.sh - - # 等待启动 - sleep 30 - - # 运行测试 - python scripts/test_cnclip_service.py --batch-size 100 -done - -# 对比结果,选择最优配置 -``` - -**推荐配置**: -- **Tesla T4 (16GB)**: 1-2 副本 -- **RTX 3090 (24GB)**: 2-3 副本 -- **A100 (40GB)**: 3-4 副本 - ---- - -## 📝 快速配置参考 - -### 修改启动脚本支持副本数 - -编辑 `scripts/start_cnclip_service.sh`,添加参数: - -```bash -# 在参数解析部分添加 ---replicas) - REPLICAS="$2" - shift 2 - ;; - -# 在启动命令中使用 -nohup python -m clip_server \ - --name "${MODEL_NAME}" \ - --replicas ${REPLICAS:-1} \ # ← 添加这一行 - --port ${PORT} \ - ... -``` - -### 当前最佳实践(Tesla T4) - -**保守配置**(推荐): -```yaml -replicas: 1 -minibatch_size: 32 -dtype: float16 -``` -- 显存:~5GB -- QPS:~80-100 -- 适合:开发、测试、小规模应用 - -**激进配置**(需测试): -```yaml -replicas: 2 -minibatch_size: 32 -dtype: float16 -``` -- 显存:~10GB -- QPS:~150-180 -- 适合:生产环境、中等并发 -- 前提:清理其他显存占用 - ---- - -## 🎉 总结 - -### 关键要点 - -1. **显存占用**:副本数 × 单副本显存(基本线性增长) -2. **性能提升**:吞吐量接近线性增长,但不完美 -3. **推荐配置**: - - Tesla T4 (16GB): **1-2 副本** - - 确保显存充足 + 预留 20% 余量 -4. **优化建议**: - - 从小副本开始测试 - - 根据实际负载调整 - - 监控显存和性能指标 - -### 你的情况 - -**当前配置**: -```yaml -replicas: 1 # 当前配置 -``` - -**建议**: -- ✅ 先保持 `replicas: 1`,测试性能 -- ✅ 如果吞吐量不够,再尝试 `replicas: 2` -- ⚠️ 确保显存充足(当前空闲2GB可能不够2副本) -- 💡 考虑先清理其他显存占用 - -**快速测试**: -```bash -# 1. 查看当前显存占用 -nvidia-smi - -# 2. 启动1副本,观察显存 -./scripts/start_cnclip_service.sh -nvidia-smi # 应该增加 ~5GB - -# 3. 如果还有空间,尝试2副本 -./scripts/stop_cnclip_service.sh -# 修改 torch-flow.yml 中的 replicas: 2 -./scripts/start_cnclip_service.sh -nvidia-smi # 应该再增加 ~5GB -``` - -需要我帮你修改启动脚本以支持副本数参数吗? diff --git a/docs/CNCLIP_SERVICE.md b/docs/CNCLIP_SERVICE.md index d54b0d2..0e4d919 100644 --- a/docs/CNCLIP_SERVICE.md +++ b/docs/CNCLIP_SERVICE.md @@ -1,24 +1,42 @@ -# CN-CLIP 编码服务使用指南 +# CN-CLIP 编码服务 -## 简介 +## 模块说明 -本服务基于 [clip-as-service](https://github.com/jina-ai/clip-as-service) 提供 CN-CLIP 模型的文本和图像编码功能。 +CN-CLIP 编码服务基于 [clip-as-service](https://github.com/jina-ai/clip-as-service) 提供中文 CLIP 模型的文本和图像编码功能。服务使用 gRPC 协议,支持批量编码,返回固定维度的向量表示。 + +### 功能特性 + +- 文本编码:将中文文本编码为向量 +- 图像编码:将图像(本地文件或远程 URL)编码为向量 +- 混合编码:同时编码文本和图像 +- 批量处理:支持批量编码,提高效率 + +### 技术架构 + +- **框架**: clip-as-service (基于 Jina) +- **模型**: CN-CLIP/ViT-L-14-336(默认) +- **协议**: gRPC(默认,官方推荐) +- **运行时**: PyTorch ## 启动服务 +### 基本用法 + ```bash ./scripts/start_cnclip_service.sh ``` ### 启动参数 -- `--port PORT`: 服务端口(默认:51000) -- `--device DEVICE`: 设备类型:cuda 或 cpu(默认:自动检测) -- `--batch-size SIZE`: 批处理大小(默认:32) -- `--num-workers NUM`: 预处理线程数(默认:4) -- `--dtype TYPE`: 数据类型:float16 或 float32(默认:float16) -- `--model-name NAME`: 模型名称(默认:CN-CLIP/ViT-H-14) -- `--replicas NUM`: 副本数(默认:1) +| 参数 | 说明 | 默认值 | +|------|------|--------| +| `--port PORT` | 服务端口 | 51000 | +| `--device DEVICE` | 设备类型:cuda 或 cpu | 自动检测 | +| `--batch-size SIZE` | 批处理大小 | 32 | +| `--num-workers NUM` | 预处理线程数 | 4 | +| `--dtype TYPE` | 数据类型:float16 或 float32 | float16 | +| `--model-name NAME` | 模型名称 | CN-CLIP/ViT-L-14-336 | +| `--replicas NUM` | 副本数 | 1 | ### 示例 @@ -29,133 +47,102 @@ # 指定端口和设备 ./scripts/start_cnclip_service.sh --port 52000 --device cpu -# 调整批处理大小 -./scripts/start_cnclip_service.sh --batch-size 16 --dtype float32 +# 使用其他模型 +./scripts/start_cnclip_service.sh --model-name CN-CLIP/ViT-H-14 ``` -## 停止服务 +### 停止服务 ```bash ./scripts/stop_cnclip_service.sh ``` -## 使用 API +## API 接口说明 -### 编码文本 +### Python 客户端 -```bash -curl -X POST http://localhost:51000/post \ - -H 'Content-Type: application/json' \ - -d '{ - "data": [ - {"text": "这是一个测试文本"}, - {"text": "另一个文本"} - ], - "execEndpoint": "/" - }' -``` +服务使用 gRPC 协议,必须使用 Python 客户端: -### 编码图像(远程 URL) +```python +from clip_client import Client -```bash -curl -X POST http://localhost:51000/post \ - -H 'Content-Type: application/json' \ - -d '{ - "data": [ - {"uri": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"} - ], - "execEndpoint": "/" - }' +# 创建客户端(使用 grpc:// 协议) +c = Client('grpc://localhost:51000') ``` -### 编码图像(本地文件,base64) +### 编码接口 -```bash -curl -X POST http://localhost:51000/post \ - -H 'Content-Type: application/json' \ - -d "{ - \"data\": [ - {\"blob\": \"$(base64 -w 0 /path/to/image.jpg)\"} - ], - \"execEndpoint\": \"/\" - }" -``` +#### 1. 文本编码 -### 混合编码(文本和图像) +```python +from clip_client import Client -```bash -curl -X POST http://localhost:51000/post \ - -H 'Content-Type: application/json' \ - -d '{ - "data": [ - {"text": "这是一段文本"}, - {"uri": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"} - ], - "execEndpoint": "/" - }' -``` +c = Client('grpc://localhost:51000') + +# 编码单个文本 +result = c.encode(['这是测试文本']) +print(result.shape) # (1, 1024) -## 响应格式 - -响应为 JSON 格式,编码结果在 `data[].embedding` 字段中: - -```json -{ - "header": {...}, - "data": [ - { - "id": "...", - "text": "这是一个测试文本", - "embedding": [0.123, -0.456, ...] - } - ] -} +# 编码多个文本 +result = c.encode(['文本1', '文本2', '文本3']) +print(result.shape) # (3, 1024) ``` -### 提取 embedding +#### 2. 图像编码 -使用 `jq` 提取 embedding: +```python +# 编码远程图像 URL +result = c.encode(['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg']) +print(result.shape) # (1, 1024) -```bash -curl -X POST http://localhost:51000/post \ - -H 'Content-Type: application/json' \ - -d '{"data":[{"text": "测试"}], "execEndpoint":"/"}' | \ - jq -c '.data[] | .embedding' +# 编码本地图像文件 +result = c.encode(['/path/to/image.jpg']) +print(result.shape) # (1, 1024) ``` -## Python 客户端示例 - -**重要**:如果服务配置了 `protocol: http`,客户端必须使用 `http://` 而不是 `grpc://`。 +#### 3. 混合编码 ```python -from clip_client import Client +# 同时编码文本和图像 +result = c.encode([ + '这是文本', + 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg', + '另一个文本' +]) +print(result.shape) # (3, 1024) +``` -# 创建客户端(注意:使用 http:// 而不是 grpc://) -c = Client('http://localhost:51000') +### 返回格式 -# 编码文本 -result = c.encode(['这是测试文本', '另一个文本']) -print(result.shape) # [2, 768] 或其他维度 +- **类型**: `numpy.ndarray` +- **形状**: `(N, 1024)`,其中 N 是输入数量 +- **数据类型**: `float32` +- **维度**: 1024(CN-CLIP 模型的 embedding 维度) -# 编码图像 -result = c.encode(['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg']) -print(result.shape) # [1, 768] +### 支持的模型 -# 混合编码 -result = c.encode([ - '这是文本', - 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg' -]) -print(result.shape) # [2, 768] +| 模型名称 | 说明 | 推荐场景 | +|---------|------|---------| +| `CN-CLIP/ViT-B-16` | 基础版本,速度快 | 对速度要求高的场景 | +| `CN-CLIP/ViT-L-14` | 平衡版本 | 通用场景 | +| `CN-CLIP/ViT-L-14-336` | 高分辨率版本(默认) | 需要处理高分辨率图像 | +| `CN-CLIP/ViT-H-14` | 大型版本,精度高 | 对精度要求高的场景 | +| `CN-CLIP/RN50` | ResNet-50 版本 | 兼容性场景 | + +## 测试 + +运行测试脚本: + +```bash +./scripts/test_cnclip_service.sh ``` -## 支持的模型 +测试脚本会验证: +- 文本编码功能 +- 图像编码功能(远程 URL) +- 混合编码功能 -- `CN-CLIP/ViT-B-16`: 基础版本,速度快 -- `CN-CLIP/ViT-L-14`: 平衡版本 -- `CN-CLIP/ViT-L-14-336`: 高分辨率版本 -- `CN-CLIP/ViT-H-14`: 大型版本,精度高(默认) -- `CN-CLIP/RN50`: ResNet-50 版本 +每个测试会显示 embedding 的维度和前 20 个数字。 ## 查看日志 @@ -165,36 +152,36 @@ tail -f /data/tw/SearchEngine/logs/cnclip_service.log ## 常见问题 -### 服务启动失败 +### 1. 服务启动失败 -1. 检查端口是否被占用:`lsof -i :51000` -2. 检查 conda 环境是否正确激活 -3. 查看日志文件获取详细错误信息 +- 检查端口是否被占用:`lsof -i :51000` +- 检查 conda 环境是否正确激活 +- 查看日志文件获取详细错误信息 -### 编码失败 +### 2. 客户端连接失败 -1. 确保请求格式正确,使用 `/post` 端点 -2. 确保 `execEndpoint` 设置为 `"/"` -3. 检查图像 URL 是否可访问 -4. 查看服务日志排查错误 - -### 协议不匹配 - -如果服务配置了 `protocol: http`,客户端必须使用 `http://` 而不是 `grpc://`: +确保使用正确的协议: ```python -# 正确 -c = Client('http://localhost:51000') - -# 错误(会导致连接失败) +# 正确:使用 grpc:// c = Client('grpc://localhost:51000') + +# 错误:不要使用 http:// +# c = Client('http://localhost:51000') # 会失败 ``` -### 图像编码问题 +### 3. 编码失败 + +- 检查服务是否正常运行 +- 检查输入格式是否正确 +- 查看服务日志排查错误 + +### 4. 依赖安装 -CN-CLIP 模型的图像编码可能存在兼容性问题。如果遇到 `AttributeError: 'str' object has no attribute 'to'` 错误,这可能是 clip-as-service 对 CN-CLIP 图像预处理的支持问题。建议: +确保已安装必要的依赖: -1. 检查 clip-as-service 和 cn-clip 的版本兼容性 -2. 尝试使用本地图像文件而不是远程 URL -3. 查看 [clip-as-service 的 GitHub Issues](https://github.com/jina-ai/clip-as-service/issues) 是否有相关报告 +```bash +pip install clip-client +``` +服务端依赖会在启动脚本中自动检查。 diff --git a/docs/GIT_SUBMODULE_GUIDE.md b/docs/GIT_SUBMODULE_GUIDE.md deleted file mode 100644 index 92dcbe9..0000000 --- a/docs/GIT_SUBMODULE_GUIDE.md +++ /dev/null @@ -1,323 +0,0 @@ -# Git 子模块处理指南 - -## 问题说明 - -当前 `third-party/clip-as-service` 是一个 Git 子模块,指向远程仓库: -- **原始仓库**: https://github.com/jina-ai/clip-as-service.git -- **问题**: 你修改了代码,但不想/无法提交到原仓库 -- **目标**: 将修改纳入自己的主项目管理 - -## 解决方案 - -我们提供三个方案,根据你的需求选择: - -### 方案 A:移除子模块,直接纳入主项目(推荐) - -**适用场景**: -- 你不需要跟随上游更新 -- 你想完全控制这些代码 -- 你会修改这些代码 - -**优点**: -- ✅ 简单直接,完全控制 -- ✅ 所有代码在一个仓库中,易于管理 -- ✅ 修改可以直接提交到主项目 - -**缺点**: -- ❌ 无法方便地获取上游更新 -- ❌ 会增加主仓库大小 - -**操作步骤**: - -```bash -# 1. 进入主项目目录 -cd /data/tw/SearchEngine - -# 2. 移除 Git 对子模块的追踪 -git rm --cached third-party/clip-as-service - -# 3. 删除 .gitmodules 中的配置(如果有) -# 编辑 .gitmodules 文件,删除 clip-as-service 相关部分 - -# 4. 删除子模块的 Git 仓库 -rm -rf third-party/clip-as-service/.git - -# 5. 将子模块目录作为普通目录添加到 Git -git add third-party/clip-as-service/ - -# 6. 提交更改 -git commit -m "feat: 将 clip-as-service 从子模块转为普通目录" - -# 7. 推送到远程仓库 -git push origin main -``` - -**后续操作**: -- 之后对 `third-party/clip-as-service` 的任何修改都可以正常提交 -- 使用 `git add` 和 `git commit` 就可以了 - ---- - -### 方案 B:切换到自己的 Fork - -**适用场景**: -- 你需要跟随上游更新 -- 你想保持 Git 历史记录 -- 你可能向原仓库贡献代码 - -**优点**: -- ✅ 可以合并上游更新 -- ✅ 保持 Git 追踪 -- ✅ 可以参与上游社区 - -**缺点**: -- ❌ 需要 Fork 并维护自己的仓库 -- ❌ 操作相对复杂 - -**操作步骤**: - -```bash -# 1. 在 GitHub 上 Fork 原仓库 -# 访问 https://github.com/jina-ai/clip-as-service -# 点击 Fork 按钮,创建你自己的副本 - -# 2. 克隆你的 Fork(替换为你的用户名) -# 注意:这里使用 --mirror 保留所有分支和标签 -git clone --mirror https://github.com/YOUR_USERNAME/clip-as-service.git - -# 3. 进入主项目目录 -cd /data/tw/SearchEngine - -# 4. 更新 .gitmodules 文件 -# 编辑 .gitmodules,将 URL 改为你的 Fork: -# [submodule "third-party/clip-as-service"] -# path = third-party/clip-as-service -# url = https://github.com/YOUR_USERNAME/clip-as-service.git - -# 5. 初始化新的子模块 -git submodule deinit -f third-party/clip-as-service -git submodule update --init --remote third-party/clip-as-service - -# 6. 进入子模块目录,设置你的 fork 为默认远程 -cd third-party/clip-as-service -git remote set-url origin https://github.com/YOUR_USERNAME/clip-as-service.git -git remote add upstream https://github.com/jina-ai/clip-as-service.git - -# 7. 创建并切换到你的分支 -git checkout -b custom-cnclip-support - -# 8. 提交你的修改 -git add . -git commit -m "feat: 添加 CN-CLIP 自定义配置" - -# 9. 推送到你的 Fork -git push origin custom-cnclip-support - -# 10. 回到主项目,更新子模块引用 -cd /data/tw/SearchEngine -git add third-party/clip-as-service -git commit -m "chore: 更新子模块到自定义版本" -git push origin main -``` - -**后续操作**: -- 在子模块目录中修改代码 -- 提交到你的分支:`git push origin custom-cnclip-support` -- 合并上游更新: - ```bash - cd third-party/clip-as-service - git fetch upstream - git merge upstream/main - git push origin custom-cnclip-support - cd .. - git add third-party/clip-as-service - git commit -m "chore: 合并上游更新" - ``` - ---- - -### 方案 C:使用 Git Subtree 替代 Submodule - -**适用场景**: -- 你想要子模块的灵活性,但不想处理 submodule 的复杂性 -- 你想直接在主项目中管理代码 - -**优点**: -- ✅ 比 submodule 更易用 -- ✅ 可以直接提交到主项目 -- ✅ 可以获取上游更新 - -**缺点**: -- ❌ 命令相对复杂 -- ❌ 重写现有历史 - -**操作步骤**: - -```bash -# 1. 移除现有子模块 -cd /data/tw/SearchEngine -git submodule deinit -f third-party/clip-as-service -git rm -f third-party/clip-as-service -rm -rf .git/modules/third-party/clip-as-service - -# 2. 使用 subtree 添加远程仓库 -git subtree add --prefix=third-party/clip-as-service \ - https://github.com/jina-ai/clip-as-service.git \ - main --squash - -# 3. 提交 -git commit -m "chore: 使用 git subtree 替代 submodule 添加 clip-as-service" - -# 4. 推送 -git push origin main -``` - -**后续操作**: -- 修改代码后直接在主项目中提交 -- 获取上游更新: - ```bash - git subtree pull --prefix=third-party/clip-as-service \ - https://github.com/jina-ai/clip-as-service.git \ - main --squash - ``` - ---- - -## 推荐方案 - -根据你的情况,我**推荐方案 A**,原因如下: - -1. **你的需求明确**:修改 CN-CLIP 配置,提供推理服务 -2. **不需要上游更新**:CLIP-as-service 已经很稳定 -3. **最简单直接**:不需要维护额外的仓库 -4. **易于管理**:所有代码在一个地方 - -## 执行方案 A 的详细步骤 - -```bash -#!/bin/bash -# 这个脚本会自动执行方案 A 的所有步骤 - -set -e - -echo "==========================================" -echo "将 clip-as-service 从子模块转为普通目录" -echo "==========================================" - -cd /data/tw/SearchEngine - -# 1. 备份当前状态(可选) -echo "步骤 1: 备份当前配置..." -if [ -f ".gitmodules" ]; then - cp .gitmodules .gitmodules.backup - echo "✓ 已备份 .gitmodules" -fi - -# 2. 移除 Git 对子模块的追踪 -echo "步骤 2: 移除子模块追踪..." -git rm --cached third-party/clip-as-service || true - -# 3. 删除 .gitmodules 中的子模块配置 -echo "步骤 3: 清理 .gitmodules..." -if [ -f ".gitmodules" ]; then - # 使用 sed 删除 clip-as-service 相关的配置块 - sed -i '/clip-as-service/,+3 d' .gitmodules - - # 如果文件为空,删除它 - if [ ! -s ".gitmodules" ]; then - rm .gitmodules - echo "✓ 已删除空的 .gitmodules 文件" - else - echo "✓ 已更新 .gitmodules" - fi -fi - -# 4. 删除子模块的 Git 仓库 -echo "步骤 4: 删除子模块 Git 仓库..." -rm -rf third-party/clip-as-service/.git -echo "✓ 已删除子模块 Git 仓库" - -# 5. 将子模块目录作为普通目录添加 -echo "步骤 5: 将目录添加到 Git..." -git add third-party/clip-as-service/ -git add .gitmodules 2>/dev/null || true -echo "✓ 已添加到 Git" - -# 6. 显示状态 -echo "" -echo "==========================================" -echo "操作完成!当前状态:" -echo "==========================================" -git status - -echo "" -echo "==========================================" -echo "下一步:" -echo "==========================================" -echo "1. 检查修改: git diff --cached" -echo "2. 提交更改: git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'" -echo "3. 推送到远程: git push origin main" -echo "" -``` - -## 验证转换成功 - -转换后,你应该可以: - -```bash -# 1. 正常查看文件状态 -git status - -# 2. 修改文件后直接添加 -# 编辑 third-party/clip-as-service/server/clip_server/torch-flow.yml -git add third-party/clip-as-service/server/clip_server/torch-flow.yml -git commit -m "config: 更新 CN-CLIP 配置" - -# 3. 推送到远程 -git push origin main - -# 4. 其他人克隆你的仓库后,不需要特殊的子模块命令 -# git clone https://your-repo-url -# cd SearchEngine -# 所有文件都已经存在,包括 third-party/clip-as-service -``` - -## 常见问题 - -**Q: 转换后会增加仓库大小吗?** -A: 会。clip-as-service 大约几十 MB。如果你担心仓库大小,可以使用 Git LFS 或 .gitignore 排除不必要的文件(如模型权重)。 - -**Q: 如何后续获取原仓库的更新?** -A: 使用方案 A 后,需要手动合并更新: -```bash -cd third-party/clip-as-service -git remote add upstream https://github.com/jina-ai/clip-as-service.git -git fetch upstream -git merge upstream/main -``` - -**Q: 我可以回退到子模块吗?** -A: 可以,但会比较复杂。建议在转换前提交一个保存点: -```bash -git branch backup-before-submodule-removal -``` - -**Q: 其他协作者需要注意什么?** -A: 他们需要重新克隆仓库,或者: -```bash -# 删除旧的子模块引用 -git submodule deinit -f third-party/clip-as-service -rm -rf .git/modules/third-party/clip-as-service - -# 重新拉取 -git pull origin main -``` - -## 总结 - -- **推荐**: 方案 A(移除子模块) -- **适用**: 你的使用场景 -- **优势**: 简单、直接、易于管理 -- **成本**: 略微增加仓库大小 - -如果需要帮助执行这些步骤,请告诉我! diff --git a/examples/clip_rest_api.py b/examples/clip_rest_api.py deleted file mode 100644 index 0b5d846..0000000 --- a/examples/clip_rest_api.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python3 -""" -CN-CLIP REST API 包装器 - -提供 HTTP 接口,支持 curl 调用 -""" - -from flask import Flask, request, jsonify -from flask_cors import CORS -from clip_client import Client -import numpy as np -import traceback - -app = Flask(__name__) -CORS(app) # 允许跨域请求 - -# 连接到 CN-CLIP 服务 -try: - client = Client('grpc://localhost:51000') - print("✓ 已连接到 CN-CLIP 服务 (grpc://localhost:51000)") -except Exception as e: - print(f"✗ 连接失败: {e}") - print("请先启动 CN-CLIP 服务: ./scripts/start_cnclip_service.sh") - client = None - - -@app.route('/health', methods=['GET']) -def health(): - """健康检查""" - return jsonify({ - 'status': 'ok' if client else 'error', - 'service': 'cnclip-rest-api', - 'backend': 'grpc://localhost:51000' - }) - - -@app.route('/encode/text', methods=['POST']) -def encode_text(): - """ - 编码文本 - - 请求体: - { - "texts": ["文本1", "文本2"] - } - - 返回: - { - "count": 2, - "shape": [2, 1024], - "embeddings": [[...], [...]] - } - """ - if not client: - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503 - - try: - data = request.json - texts = data.get('texts', []) - - if not texts: - return jsonify({'error': '缺少 texts 参数'}), 400 - - # 编码 - embeddings = client.encode(texts) - - return jsonify({ - 'count': len(texts), - 'shape': embeddings.shape.tolist(), - 'embeddings': embeddings.tolist() - }) - - except Exception as e: - print(f"错误: {e}") - print(traceback.format_exc()) - return jsonify({'error': str(e)}), 500 - - -@app.route('/encode/image', methods=['POST']) -def encode_image(): - """ - 编码图像 - - 请求体: - { - "images": ["https://example.com/image.jpg", "/path/to/local.jpg"] - } - - 返回: - { - "count": 2, - "shape": [2, 1024], - "embeddings": [[...], [...]] - } - """ - if not client: - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503 - - try: - data = request.json - images = data.get('images', []) - - if not images: - return jsonify({'error': '缺少 images 参数'}), 400 - - # 编码 - embeddings = client.encode(images) - - return jsonify({ - 'count': len(images), - 'shape': embeddings.shape.tolist(), - 'embeddings': embeddings.tolist() - }) - - except Exception as e: - print(f"错误: {e}") - print(traceback.format_exc()) - return jsonify({'error': str(e)}), 500 - - -@app.route('/encode/mixed', methods=['POST']) -def encode_mixed(): - """ - 混合编码(文本+图像) - - 请求体: - { - "data": ["文本", "https://example.com/image.jpg"] - } - - 返回: - { - "count": 2, - "shape": [2, 1024], - "embeddings": [[...], [...]] - } - """ - if not client: - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503 - - try: - data = request.json - mixed_data = data.get('data', []) - - if not mixed_data: - return jsonify({'error': '缺少 data 参数'}), 400 - - # 编码 - embeddings = client.encode(mixed_data) - - return jsonify({ - 'count': len(mixed_data), - 'shape': embeddings.shape.tolist(), - 'embeddings': embeddings.tolist() - }) - - except Exception as e: - print(f"错误: {e}") - print(traceback.format_exc()) - return jsonify({'error': str(e)}), 500 - - -@app.route('/similarity', methods=['POST']) -def similarity(): - """ - 计算相似度 - - 请求体: - { - "text": "查询文本", - "images": ["url1", "url2"], - "texts": ["文本1", "文本2"] - } - - 返回: - { - "image_similarities": [0.8, 0.3], - "text_similarities": [0.9, 0.2] - } - """ - if not client: - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503 - - try: - data = request.json - query_text = data.get('text', '') - images = data.get('images', []) - texts = data.get('texts', []) - - if not query_text: - return jsonify({'error': '缺少 text 参数'}), 400 - - from sklearn.metrics.pairwise import cosine_similarity - - # 编码查询文本 - query_embedding = client.encode([query_text]) - - result = {} - - # 计算与图像的相似度 - if images: - image_embeddings = client.encode(images) - similarities = cosine_similarity(query_embedding, image_embeddings)[0] - result['image_similarities'] = similarities.tolist() - result['image_urls'] = images - - # 计算与文本的相似度 - if texts: - text_embeddings = client.encode(texts) - similarities = cosine_similarity(query_embedding, text_embeddings)[0] - result['text_similarities'] = similarities.tolist() - result['texts'] = texts - - return jsonify(result) - - except Exception as e: - print(f"错误: {e}") - print(traceback.format_exc()) - return jsonify({'error': str(e)}), 500 - - -@app.errorhandler(404) -def not_found(error): - return jsonify({'error': '接口不存在'}), 404 - - -@app.errorhandler(500) -def internal_error(error): - return jsonify({'error': '服务器内部错误'}), 500 - - -if __name__ == '__main__': - print("\n" + "=" * 60) - print("CN-CLIP REST API 服务") - print("=" * 60) - print("\n服务地址: http://localhost:6000") - print("\n可用接口:") - print(" POST /health - 健康检查") - print(" POST /encode/text - 编码文本") - print(" POST /encode/image - 编码图像") - print(" POST /encode/mixed - 混合编码") - print(" POST /similarity - 计算相似度") - print("\n示例:") - print(" curl http://localhost:6000/health") - print(" curl -X POST http://localhost:6000/encode/text -H 'Content-Type: application/json' -d '{\"texts\": [\"测试文本\"]}'") - print("\n" + "=" * 60) - print() - - app.run( - host='0.0.0.0', - port=6000, - debug=True, - use_reloader=False # 避免重复启动 - ) diff --git a/examples/simple_examples.py b/examples/simple_examples.py deleted file mode 100644 index 1a194cf..0000000 --- a/examples/simple_examples.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -""" -CN-CLIP 简单示例 - -最常用的文本和图像编码示例 -""" - -from clip_client import Client - -# 初始化客户端 -client = Client('grpc://localhost:51000') - -# ============================================================================ -# 示例 1: 编码文本 -# ============================================================================ -print("示例 1: 文本编码") -print("-" * 50) - -texts = ['一只可爱的猫咪', '美丽的高山风景'] -embeddings = client.encode(texts) - -print(f"输入: {texts}") -print(f"输出形状: {embeddings.shape}") # (2, 1024) -print(f"✓ 编码完成\n") - -# ============================================================================ -# 示例 2: 编码图像(URL) -# ============================================================================ -print("示例 2: 图像编码(URL)") -print("-" * 50) - -image_url = "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg" -embedding = client.encode([image_url]) - -print(f"输入: {image_url}") -print(f"输出形状: {embedding.shape}") # (1, 1024) -print(f"✓ 编码完成\n") - -# ============================================================================ -# 示例 3: 编码图像(本地路径) -# ============================================================================ -print("示例 3: 图像编码(本地文件)") -print("-" * 50) - -local_image = "/path/to/local/image.jpg" -# embedding = client.encode([local_image]) -print(f"输入: {local_image}") -print(f"用法: client.encode(['{local_image}'])") -print(f"✓ 编码完成\n") - -# ============================================================================ -# 示例 4: 混合编码(文本+图像) -# ============================================================================ -print("示例 4: 混合编码") -print("-" * 50) - -mixed_data = [ - '一只可爱的猫咪', # 文本 - 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg', # 图像URL -] - -embeddings = client.encode(mixed_data) -print(f"输入: {mixed_data}") -print(f"输出形状: {embeddings.shape}") # (2, 1024) -print(f"✓ 编码完成\n") - -# ============================================================================ -# 示例 5: 批量编码 -# ============================================================================ -print("示例 5: 批量编码(推荐)") -print("-" * 50) - -# 一次编码多条数据(更高效) -batch_data = [f"文本 {i}" for i in range(10)] -embeddings = client.encode(batch_data) - -print(f"输入: {len(batch_data)} 条文本") -print(f"输出形状: {embeddings.shape}") # (10, 1024) -print(f"✓ 批量编码完成\n") - -# ============================================================================ -# 重要提示 -# ============================================================================ -print("重要提示:") -print("-" * 50) -print("1. 输入必须是列表: client.encode(['文本']) ✓") -print(" 不是单个字符串: client.encode('文本') ✗") -print() -print("2. 返回值是 numpy 数组,形状为 (N, 1024)") -print(" N = 输入数据的数量") -print() -print("3. 图像支持 URL 和本地文件路径") -print() diff --git a/examples/test_cnclip_example.py b/examples/test_cnclip_example.py deleted file mode 100644 index fc8aa6a..0000000 --- a/examples/test_cnclip_example.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python3 -""" -CN-CLIP 快速测试脚本 - -测试文本和图像编码功能 -""" - -from clip_client import Client -from sklearn.metrics.pairwise import cosine_similarity -import numpy as np - -# 测试图片 -TEST_IMAGE = "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg" - -# 测试文本 -TEST_TEXTS = [ - "一只可爱的猫咪", - "美丽的高山风景", - "汽车在公路上行驶", - "现代建筑", -] - -def test_connection(): - """测试服务连接""" - print("=" * 60) - print("测试 1: 连接服务") - print("=" * 60) - - try: - client = Client('grpc://localhost:51000') - print("✓ 服务连接成功") - return client - except Exception as e: - print(f"✗ 连接失败: {e}") - print("\n请确保服务已启动:") - print(" ./scripts/start_cnclip_service.sh") - return None - -def test_text_encoding(client): - """测试文本编码""" - print("\n" + "=" * 60) - print("测试 2: 文本编码") - print("=" * 60) - - print(f"\n测试文本:") - for i, text in enumerate(TEST_TEXTS, 1): - print(f" {i}. {text}") - - try: - embeddings = client.encode(TEST_TEXTS) - print(f"\n✓ 文本编码成功") - print(f" 编码数量: {len(embeddings)}") - print(f" 向量形状: {embeddings.shape}") - print(f" 数据类型: {embeddings.dtype}") - print(f" 值域: [{embeddings.min():.4f}, {embeddings.max():.4f}]") - return embeddings - except Exception as e: - print(f"✗ 文本编码失败: {e}") - return None - -def test_image_encoding(client): - """测试图像编码""" - print("\n" + "=" * 60) - print("测试 3: 图像编码") - print("=" * 60) - - print(f"\n测试图片: {TEST_IMAGE}") - - try: - embeddings = client.encode([TEST_IMAGE]) - print(f"\n✓ 图像编码成功") - print(f" 向量形状: {embeddings.shape}") - print(f" 数据类型: {embeddings.dtype}") - print(f" 值域: [{embeddings.min():.4f}, {embeddings.max():.4f}]") - return embeddings - except Exception as e: - print(f"✗ 图像编码失败: {e}") - return None - -def test_image_text_retrieval(client, image_embedding, text_embeddings): - """测试图文检索""" - print("\n" + "=" * 60) - print("测试 4: 图文检索(计算相似度)") - print("=" * 60) - - print(f"\n使用图片搜索最匹配的文本...") - - try: - # 计算相似度 - similarities = cosine_similarity(image_embedding, text_embeddings)[0] - - print(f"\n相似度排序:") - # 按相似度排序 - sorted_indices = np.argsort(similarities)[::-1] - - for rank, idx in enumerate(sorted_indices, 1): - text = TEST_TEXTS[idx] - score = similarities[idx] - bar = "█" * int(score * 50) - print(f" {rank}. {score:.4f} {bar} {text}") - - print(f"\n最佳匹配: {TEST_TEXTS[sorted_indices[0]]}") - print(f"相似度分数: {similarities[sorted_indices[0]]:.4f}") - - return similarities - except Exception as e: - print(f"✗ 相似度计算失败: {e}") - return None - -def test_batch_encoding(client): - """测试批量编码""" - print("\n" + "=" * 60) - print("测试 5: 批量编码性能") - print("=" * 60) - - import time - - # 准备测试数据 - batch_texts = [f"测试文本 {i}" for i in range(50)] - - print(f"\n编码 {len(batch_texts)} 条文本...") - - try: - start = time.time() - embeddings = client.encode(batch_texts) - elapsed = time.time() - start - - print(f"\n✓ 批量编码成功") - print(f" 耗时: {elapsed:.2f}秒") - print(f" 速度: {len(batch_texts)/elapsed:.2f} 条/秒") - print(f" 平均延迟: {elapsed/len(batch_texts)*1000:.2f}ms/条") - - except Exception as e: - print(f"✗ 批量编码失败: {e}") - -def main(): - print("\n" + "=" * 60) - print("CN-CLIP 服务测试") - print("=" * 60) - print(f"\n测试图片: {TEST_IMAGE}") - print(f"服务地址: grpc://localhost:51000") - - # 测试连接 - client = test_connection() - if not client: - return - - # 测试文本编码 - text_embeddings = test_text_encoding(client) - if text_embeddings is None: - return - - # 测试图像编码 - image_embeddings = test_image_encoding(client) - if image_embeddings is None: - return - - # 测试图文检索 - test_image_text_retrieval(client, image_embeddings, text_embeddings) - - # 测试批量编码性能 - test_batch_encoding(client) - - # 总结 - print("\n" + "=" * 60) - print("测试总结") - print("=" * 60) - print("\n✓ 所有测试通过!") - print("\n服务运行正常,可以开始使用。") - print("\n下一步:") - print(" 1. 查看使用文档: cat docs/CNCLIP_USAGE_GUIDE.md") - print(" 2. 集成到你的项目") - print(" 3. 调整服务配置(如需要)") - print() - -if __name__ == '__main__': - main() diff --git a/examples/test_curl_examples.sh b/examples/test_curl_examples.sh deleted file mode 100755 index 5090674..0000000 --- a/examples/test_curl_examples.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/bash - -############################################################################### -# CN-CLIP REST API 快速测试脚本 -# -# 用途: -# 测试 REST API 的各种功能 -# -# 使用方法: -# ./examples/test_curl_examples.sh -# -############################################################################### - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -API_URL="http://localhost:51000" -TEST_IMAGE="https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}CN-CLIP REST API 测试${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# 测试 1: 健康检查 -echo -e "${BLUE}测试 1: 健康检查${NC}" -echo "curl ${API_URL}/health" -echo "" - -# 测试 2: 编码文本 -echo -e "${BLUE}测试 2: 编码文本${NC}" -echo "curl -X POST ${API_URL}/encode/text \\" -echo " -H 'Content-Type: application/json' \\" -echo " -d '{\"texts\": [\"一只可爱的猫咪\"]}'" -echo "" - -response=$(curl -s -X POST "${API_URL}/encode/text" \ - -H "Content-Type: application/json" \ - -d '{"texts": ["一只可爱的猫咪"]}') - -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response" - -if echo "$response" | grep -q '"shape": \[1, 1024\]'; then - echo -e "${GREEN}✓ 文本编码成功${NC}" -else - echo -e "${RED}✗ 文本编码失败${NC}" -fi - -echo "" -echo "按 Enter 继续..." -read - -# 测试 3: 编码图像 -echo -e "${BLUE}测试 3: 编码图像${NC}" -echo "curl -X POST ${API_URL}/encode/image \\" -echo " -H 'Content-Type: application/json' \\" -echo " -d '{\"images\": [\"${TEST_IMAGE}\"]}'" -echo "" - -response=$(curl -s -X POST "${API_URL}/encode/image" \ - -H "Content-Type: application/json" \ - -d "{\"images\": [\"${TEST_IMAGE}\"]}") - -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response" - -if echo "$response" | grep -q '"shape": \[1, 1024\]'; then - echo -e "${GREEN}✓ 图像编码成功${NC}" -else - echo -e "${RED}✗ 图像编码失败${NC}" -fi - -echo "" -echo "按 Enter 继续..." -read - -# 测试 4: 批量编码 -echo -e "${BLUE}测试 4: 批量编码${NC}" -echo "curl -X POST ${API_URL}/encode/text \\" -echo " -H 'Content-Type: application/json' \\" -echo " -d '{\"texts\": [\"文本1\", \"文本2\", \"文本3\"]}'" -echo "" - -response=$(curl -s -X POST "${API_URL}/encode/text" \ - -H "Content-Type: application/json" \ - -d '{"texts": ["文本1", "文本2", "文本3"]}') - -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response" - -if echo "$response" | grep -q '"shape": \[3, 1024\]'; then - echo -e "${GREEN}✓ 批量编码成功${NC}" -else - echo -e "${RED}✗ 批量编码失败${NC}" -fi - -echo "" -echo "按 Enter 继续..." -read - -# 测试 5: 相似度计算 -echo -e "${BLUE}测试 5: 相似度计算${NC}" -echo "curl -X POST ${API_URL}/similarity \\" -echo " -H 'Content-Type: application/json' \\" -echo " -d '{\"text\": \"可爱的猫咪\", \"texts\": [\"一只可爱的小猫\", \"美丽的高山\"]}'" -echo "" - -response=$(curl -s -X POST "${API_URL}/similarity" \ - -H "Content-Type: application/json" \ - -d '{"text": "可爱的猫咪", "texts": ["一只可爱的小猫", "美丽的高山"]}') - -echo "$response" | python3 -m json.tool 2>/dev/null || echo "$response" - -if echo "$response" | grep -q '"text_similarities"'; then - echo -e "${GREEN}✓ 相似度计算成功${NC}" -else - echo -e "${RED}✗ 相似度计算失败${NC}" -fi - -echo "" -echo "按 Enter 继续..." -read - -# 测试 6: 混合编码 -echo -e "${BLUE}测试 6: 混合编码(文本+图像)${NC}" -echo "curl -X POST ${API_URL}/encode/mixed \\" -echo " -H 'Content-Type: application/json' \\" -echo " -d '{\"data\": [\"一只可爱的猫咪\", \"${TEST_IMAGE}\"]}'" -echo "" - -response=$(curl -s -X POST "${API_URL}/encode/mixed" \ - -H "Content-Type: application/json" \ - -d "{\"data\": [\"一只可爱的猫咪\", \"${TEST_IMAGE}\"]}") - -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response" - -if echo "$response" | grep -q '"shape": \[2, 1024\]'; then - echo -e "${GREEN}✓ 混合编码成功${NC}" -else - echo -e "${RED}✗ 混合编码失败${NC}" -fi - -# 总结 -echo "" -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}测试总结${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" -echo -e "${GREEN}✓ 所有测试完成!${NC}" -echo "" -echo -e "下一步:" -echo " 1. 查看使用文档: cat docs/CNCLIP_CURL_GUIDE.md" -echo " 2. 在你的代码中调用 API" -echo " 3. 集成到你的应用" -echo "" diff --git a/scripts/compare_index_mappings.py b/scripts/compare_index_mappings.py deleted file mode 100644 index 7554e56..0000000 --- a/scripts/compare_index_mappings.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -""" -对比不同租户索引的 mapping 结构 -""" - -import os -import sys -import json -from pathlib import Path -from typing import Dict, Any - -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from utils.es_client import get_es_client_from_env - - -def get_field_type(mapping_dict: Dict, field_path: str) -> Dict[str, Any]: - """递归获取字段的 mapping 信息""" - parts = field_path.split('.') - current = mapping_dict - - for part in parts: - if isinstance(current, dict): - current = current.get(part) - if current is None: - return None - else: - return None - return current - - -def compare_mappings(mapping1: Dict[str, Any], mapping2: Dict[str, Any], index1_name: str, index2_name: str): - """对比两个索引的 mapping""" - props1 = mapping1.get('mappings', {}).get('properties', {}) - props2 = mapping2.get('mappings', {}).get('properties', {}) - - all_fields = set(props1.keys()) | set(props2.keys()) - - print(f"\n{'='*80}") - print(f"对比索引映射结构") - print(f"{'='*80}") - print(f"索引1: {index1_name}") - print(f"索引2: {index2_name}") - print(f"{'='*80}\n") - - differences = [] - same_fields = [] - - for field in sorted(all_fields): - field1 = props1.get(field) - field2 = props2.get(field) - - if field1 is None: - differences.append((field, f"只在 {index2_name} 中存在", field2)) - continue - if field2 is None: - differences.append((field, f"只在 {index1_name} 中存在", field1)) - continue - - type1 = field1.get('type') - type2 = field2.get('type') - - if type1 != type2: - differences.append((field, f"类型不同: {index1_name}={type1}, {index2_name}={type2}", (field1, field2))) - else: - same_fields.append((field, type1)) - - # 打印相同的字段 - print(f"✓ 相同字段 ({len(same_fields)} 个):") - for field, field_type in same_fields[:20]: # 只显示前20个 - print(f" - {field}: {field_type}") - if len(same_fields) > 20: - print(f" ... 还有 {len(same_fields) - 20} 个相同字段") - - # 打印不同的字段 - if differences: - print(f"\n✗ 不同字段 ({len(differences)} 个):") - for field, reason, details in differences: - print(f"\n {field}:") - print(f" {reason}") - if isinstance(details, tuple): - print(f" {index1_name}: {json.dumps(details[0], indent=4, ensure_ascii=False)}") - print(f" {index2_name}: {json.dumps(details[1], indent=4, ensure_ascii=False)}") - else: - print(f" 详情: {json.dumps(details, indent=4, ensure_ascii=False)}") - else: - print(f"\n✓ 所有字段类型一致!") - - # 特别检查 tags 字段 - print(f"\n{'='*80}") - print(f"特别检查: tags 字段") - print(f"{'='*80}") - - tags1 = get_field_type(props1, 'tags') - tags2 = get_field_type(props2, 'tags') - - if tags1: - print(f"\n{index1_name}.tags:") - print(f" 类型: {tags1.get('type')}") - print(f" 完整定义: {json.dumps(tags1, indent=2, ensure_ascii=False)}") - else: - print(f"\n{index1_name}.tags: 不存在") - - if tags2: - print(f"\n{index2_name}.tags:") - print(f" 类型: {tags2.get('type')}") - print(f" 完整定义: {json.dumps(tags2, indent=2, ensure_ascii=False)}") - else: - print(f"\n{index2_name}.tags: 不存在") - - -def main(): - import argparse - - parser = argparse.ArgumentParser(description='对比 Elasticsearch 索引的 mapping 结构') - parser.add_argument('index1', help='第一个索引名称 (例如: search_products_tenant_171)') - parser.add_argument('index2', nargs='?', help='第二个索引名称 (例如: search_products_tenant_162)') - parser.add_argument('--list', action='store_true', help='列出所有以 index1 为前缀的索引') - - args = parser.parse_args() - - # 连接 ES - try: - es_client = get_es_client_from_env() - if not es_client.ping(): - print("✗ 无法连接到 Elasticsearch") - return 1 - print("✓ Elasticsearch 连接成功\n") - except Exception as e: - print(f"✗ 连接 Elasticsearch 失败: {e}") - return 1 - - # 如果指定了 --list,列出所有匹配的索引 - if args.list or not args.index2: - try: - # 使用 cat API 列出所有索引 - indices = es_client.client.cat.indices(format='json') - matching_indices = [idx['index'] for idx in indices if idx['index'].startswith(args.index1)] - - if matching_indices: - print(f"找到 {len(matching_indices)} 个匹配的索引:") - for idx in sorted(matching_indices): - print(f" - {idx}") - return 0 - else: - print(f"未找到以 '{args.index1}' 开头的索引") - return 1 - except Exception as e: - print(f"✗ 列出索引失败: {e}") - return 1 - - # 获取两个索引的 mapping - index1 = args.index1 - index2 = args.index2 - - print(f"正在获取索引映射...") - print(f" 索引1: {index1}") - print(f" 索引2: {index2}\n") - - # 检查索引是否存在 - if not es_client.index_exists(index1): - print(f"✗ 索引 '{index1}' 不存在") - return 1 - - if not es_client.index_exists(index2): - print(f"✗ 索引 '{index2}' 不存在") - return 1 - - # 获取 mapping - mapping1 = es_client.get_mapping(index1) - mapping2 = es_client.get_mapping(index2) - - if not mapping1 or index1 not in mapping1: - print(f"✗ 无法获取索引 '{index1}' 的映射") - return 1 - - if not mapping2 or index2 not in mapping2: - print(f"✗ 无法获取索引 '{index2}' 的映射") - return 1 - - # 对比 mapping - compare_mappings(mapping1[index1], mapping2[index2], index1, index2) - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) - diff --git a/scripts/remove_clip_submodule.sh b/scripts/remove_clip_submodule.sh deleted file mode 100755 index f0a0217..0000000 --- a/scripts/remove_clip_submodule.sh +++ /dev/null @@ -1,210 +0,0 @@ -#!/bin/bash - -############################################################################### -# 将 clip-as-service 从 Git 子模块转为普通目录 -# -# 用途: -# 移除 Git 子模块配置,将 clip-as-service 直接纳入主项目管理 -# -# 使用方法: -# ./scripts/remove_clip_submodule.sh -# -# 注意: -# - 此操作会修改 Git 配置,请确保已提交当前更改 -# - 建议先创建备份分支 -# - 执行前请先阅读 docs/GIT_SUBMODULE_GUIDE.md -# -############################################################################### - -set -e # 遇到错误立即退出 - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 项目路径 -PROJECT_ROOT="/data/tw/SearchEngine" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}将 clip-as-service 从子模块转为普通目录${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# 检查是否在项目根目录 -if [ ! -f "${PROJECT_ROOT}/.git" ] && [ ! -d "${PROJECT_ROOT}/.git" ]; then - echo -e "${RED}错误: 当前目录不是 Git 仓库根目录${NC}" - echo -e "${YELLOW}请在项目根目录运行此脚本${NC}" - exit 1 -fi - -cd "${PROJECT_ROOT}" - -# 检查是否有未提交的更改 -if [ -n "$(git status --porcelain)" ]; then - echo -e "${RED}错误: Git 工作区有未提交的更改${NC}" - echo -e "${YELLOW}请先提交或暂存所有更改${NC}" - echo "" - echo "未提交的文件:" - git status --short - echo "" - echo -e "${YELLOW}建议操作:${NC}" - echo " 1. 提交更改: git add . && git commit -m '保存点'" - echo " 2. 或创建备份分支: git branch backup-before-submodule-removal" - exit 1 -fi - -# 确认操作 -echo -e "${YELLOW}此操作将会:${NC}" -echo " 1. 移除 clip-as-service 的子模块配置" -echo " 2. 将其作为普通目录纳入 Git 管理" -echo " 3. 更新 .gitmodules 文件" -echo "" -echo -e "${YELLOW}请确保已阅读: docs/GIT_SUBMODULE_GUIDE.md${NC}" -echo "" -read -p "$(echo -e ${YELLOW}是否继续? [y/N]: ${NC})" -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}已取消操作${NC}" - exit 0 -fi - -# 创建备份分支 -BACKUP_BRANCH="backup-before-submodule-removal-$(date +%Y%m%d-%H%M%S)" -echo -e "${BLUE}创建备份分支: ${BACKUP_BRANCH}${NC}" -git branch "${BACKUP_BRANCH}" -echo -e "${GREEN}✓ 备份分支已创建${NC}" -echo "" - -# 步骤 1: 备份 .gitmodules -echo -e "${BLUE}步骤 1: 备份配置文件...${NC}" -if [ -f ".gitmodules" ]; then - cp .gitmodules .gitmodules.backup - echo -e "${GREEN}✓ 已备份 .gitmodules → .gitmodules.backup${NC}" -fi - -# 步骤 2: 移除子模块追踪 -echo -e "${BLUE}步骤 2: 移除子模块追踪...${NC}" -if git ls-files --stage | grep -q "160000.*third-party/clip-as-service"; then - git rm --cached third-party/clip-as-service - echo -e "${GREEN}✓ 已移除子模块缓存${NC}" -else - echo -e "${YELLOW}⚠ 子模块未在 Git 索引中,跳过此步骤${NC}" -fi - -# 步骤 3: 清理 .gitmodules -echo -e "${BLUE}步骤 3: 清理 .gitmodules...${NC}" -if [ -f ".gitmodules" ]; then - # 删除 clip-as-service 相关的配置块 - # 从 [submodule "third-party/clip-as-service"] 到下一个空行或文件末尾 - sed -i '/\[submodule "third-party\/clip-as-service"\]/,/\s*$/d' .gitmodules - - # 如果文件为空或只包含空行,删除它 - if [ ! -s ".gitmodules" ] || [ $(wc -l < .gitmodules) -eq 0 ]; then - rm .gitmodules - echo -e "${GREEN}✓ 已删除空的 .gitmodules 文件${NC}" - else - echo -e "${GREEN}✓ 已更新 .gitmodules${NC}" - fi -else - echo -e "${YELLOW}⚠ .gitmodules 文件不存在,跳过此步骤${NC}" -fi - -# 步骤 4: 删除子模块的 Git 仓库 -echo -e "${BLUE}步骤 4: 删除子模块 Git 仓库...${NC}" -if [ -d "third-party/clip-as-service/.git" ]; then - rm -rf third-party/clip-as-service/.git - echo -e "${GREEN}✓ 已删除子模块 Git 仓库${NC}" -else - echo -e "${YELLOW}⚠ 子模块 Git 仓库不存在,跳过此步骤${NC}" -fi - -# 步骤 5: 清理 Git 模块缓存 -echo -e "${BLUE}步骤 5: 清理 Git 模块缓存...${NC}" -if [ -d ".git/modules/third-party/clip-as-service" ]; then - rm -rf .git/modules/third-party/clip-as-service - echo -e "${GREEN}✓ 已清理 Git 模块缓存${NC}" -else - echo -e "${YELLOW}⚠ Git 模块缓存不存在,跳过此步骤${NC}" -fi - -# 步骤 6: 将目录作为普通文件添加 -echo -e "${BLUE}步骤 6: 将目录添加到 Git...${NC}" -git add third-party/clip-as-service/ -if [ -f ".gitmodules" ]; then - git add .gitmodules -fi -echo -e "${GREEN}✓ 已添加到 Git${NC}" - -# 显示状态 -echo "" -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}操作完成!当前状态:${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" -git status -echo "" - -# 统计信息 -echo -e "${BLUE}统计信息:${NC}" -FILE_COUNT=$(find third-party/clip-as-service -type f | wc -l) -DIR_SIZE=$(du -sh third-party/clip-as-service | cut -f1) -echo " 文件数量: ${FILE_COUNT}" -echo " 目录大小: ${DIR_SIZE}" -echo "" - -# 下一步提示 -echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN}✓ 子模块转换完成!${NC}" -echo -e "${GREEN}========================================${NC}" -echo "" -echo -e "${BLUE}下一步操作:${NC}" -echo "" -echo "1. 查看将要提交的更改:" -echo " git diff --cached --stat" -echo "" -echo "2. 提交更改:" -echo " git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'" -echo "" -echo "3. 推送到远程:" -echo " git push origin main" -echo "" -echo -e "${BLUE}注意事项:${NC}" -echo " - 备份分支: ${BACKUP_BRANCH}" -echo " - 如需回退: git reset --hard ${BACKUP_BRANCH}" -echo " - 配置备份: .gitmodules.backup" -echo "" -echo -e "${BLUE}验证转换:${NC}" -echo " cd third-party/clip-as-service" -echo " git status # 应该显示 'not a git repository'" -echo "" - -# 提示查看详细文档 -echo -e "${YELLOW}📖 详细文档: docs/GIT_SUBMODULE_GUIDE.md${NC}" -echo "" - -# 询问是否立即提交 -read -p "$(echo -e ${YELLOW}是否立即提交更改? [Y/n]: ${NC})" -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]] || [ -z "$REPLY" ]; then - echo -e "${BLUE}正在提交更改...${NC}" - git commit -m "feat: 将 clip-as-service 从子模块转为普通目录 - -- 移除子模块配置,改为普通目录 -- 便于自定义配置和管理 -- 备份分支: ${BACKUP_BRANCH} -" - echo -e "${GREEN}✓ 已提交${NC}" - echo "" - echo -e "${YELLOW}现在可以推送到远程仓库:${NC}" - echo " git push origin main" -else - echo -e "${YELLOW}跳过提交${NC}" - echo -e "${YELLOW}你可以稍后手动提交:${NC}" - echo " git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'" -fi - -echo "" -echo -e "${GREEN}所有操作完成!${NC}" diff --git a/scripts/start_clip_api.sh b/scripts/start_clip_api.sh deleted file mode 100755 index ae86f93..0000000 --- a/scripts/start_clip_api.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/bash - -############################################################################### -# CN-CLIP REST API 启动脚本 -# -# 用途: -# 启动 REST API 服务,提供 HTTP 接口供 curl 调用 -# -# 使用方法: -# ./scripts/start_clip_api.sh -# -############################################################################### - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 项目路径 -PROJECT_ROOT="/data/tw/SearchEngine" -API_SCRIPT="${PROJECT_ROOT}/examples/clip_rest_api.py" -PID_FILE="${PROJECT_ROOT}/logs/clip_rest_api.pid" -LOG_FILE="${PROJECT_ROOT}/logs/clip_rest_api.log" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}启动 CN-CLIP REST API 服务${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# 检查 API 脚本 -if [ ! -f "${API_SCRIPT}" ]; then - echo -e "${RED}错误: API 脚本不存在: ${API_SCRIPT}${NC}" - exit 1 -fi - -# 创建日志目录 -mkdir -p "$(dirname "${LOG_FILE}")" - -# 检查是否已有服务运行 -if [ -f "${PID_FILE}" ]; then - OLD_PID=$(cat "${PID_FILE}") - if ps -p ${OLD_PID} > /dev/null 2>&1; then - echo -e "${YELLOW}警告: REST API 服务已在运行 (PID: ${OLD_PID})${NC}" - echo -e "${YELLOW}如需重启,请先运行: ./scripts/stop_clip_api.sh${NC}" - exit 0 - else - echo -e "${YELLOW}清理旧的 PID 文件${NC}" - rm -f "${PID_FILE}" - fi -fi - -# 检查端口是否被占用 -if lsof -Pi :6000 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo -e "${RED}错误: 端口 6000 已被占用${NC}" - echo -e "${YELLOW}请检查是否有其他服务正在使用该端口${NC}" - lsof -i :6000 | grep LISTEN || true - exit 1 -fi - -# 检查 conda 环境 -if [ -z "${CONDA_DEFAULT_ENV}" ] || [ "${CONDA_DEFAULT_ENV}" != "clip_service" ]; then - echo -e "${YELLOW}激活 clip_service 环境...${NC}" - if [ -f "/home/tw/miniconda3/etc/profile.d/conda.sh" ]; then - source "/home/tw/miniconda3/etc/profile.d/conda.sh" - conda activate clip_service - else - echo -e "${RED}错误: 无法找到 conda${NC}" - exit 1 - fi -fi - -# 检查依赖 -echo -e "${BLUE}检查依赖...${NC}" -python -c "import flask" 2>/dev/null || { - echo -e "${YELLOW}Flask 未安装,正在安装...${NC}" - pip install flask flask-cors scikit-learn -} - -# 检查 CN-CLIP 服务 -echo -e "${BLUE}检查 CN-CLIP 服务...${NC}" -if ! ps aux | grep "clip_server" | grep -v grep > /dev/null; then - echo -e "${YELLOW}警告: CN-CLIP 服务未运行${NC}" - echo -e "${YELLOW}请先启动: ./scripts/start_cnclip_service.sh${NC}" - read -p "$(echo -e ${YELLOW}是否继续启动 REST API? [y/N]: ${NC})" -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 0 - fi -fi - -# 启动 REST API 服务 -echo -e "${BLUE}正在启动 REST API 服务...${NC}" -cd "${PROJECT_ROOT}" - -nohup python "${API_SCRIPT}" > "${LOG_FILE}" 2>&1 & -API_PID=$! -echo ${API_PID} > "${PID_FILE}" - -# 等待服务启动 -echo -e "${YELLOW}等待服务启动...${NC}" -sleep 3 - -# 检查服务是否启动成功 -if ps -p ${API_PID} > /dev/null 2>&1; then - # 检查端口是否监听 - if lsof -Pi :6000 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo -e "${GREEN}========================================${NC}" - echo -e "${GREEN}✓ REST API 服务启动成功!${NC}" - echo -e "${GREEN}========================================${NC}" - echo "" - echo -e "服务信息:" - echo -e " PID: ${API_PID}" - echo -e " 端口: 6000" - echo -e " 日志文件: ${LOG_FILE}" - echo "" - echo -e "测试服务:" - echo -e " curl http://localhost:6000/health" - echo "" - echo -e "使用示例:" - echo -e " # 编码文本" - echo -e " curl -X POST http://localhost:6000/encode/text \\" - echo -e " -H 'Content-Type: application/json' \\" - echo -e " -d '{\"texts\": [\"测试文本\"]}'" - echo "" - echo -e " # 编码图像" - echo -e " curl -X POST http://localhost:6000/encode/image \\" - echo -e " -H 'Content-Type: application/json' \\" - echo -e " -d '{\"images\": [\"https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg\"]}'" - echo "" - echo -e "查看日志:" - echo -e " tail -f ${LOG_FILE}" - echo "" - echo -e "停止服务:" - echo -e " ./scripts/stop_clip_api.sh" - echo "" - else - echo -e "${YELLOW}服务已启动,但端口尚未监听,请稍候...${NC}" - echo -e "${YELLOW}查看日志: tail -f ${LOG_FILE}${NC}" - fi -else - echo -e "${RED}========================================${NC}" - echo -e "${RED}✗ 服务启动失败!${NC}" - echo -e "${RED}========================================${NC}" - echo "" - echo -e "请查看日志获取详细错误信息:" - echo -e " tail -f ${LOG_FILE}" - echo "" - rm -f "${PID_FILE}" - exit 1 -fi diff --git a/scripts/start_cnclip_service.sh b/scripts/start_cnclip_service.sh index a3f8e6a..6057f8b 100755 --- a/scripts/start_cnclip_service.sh +++ b/scripts/start_cnclip_service.sh @@ -42,6 +42,7 @@ DEFAULT_BATCH_SIZE=32 DEFAULT_NUM_WORKERS=4 DEFAULT_DTYPE="float16" DEFAULT_MODEL_NAME="CN-CLIP/ViT-H-14" +# DEFAULT_MODEL_NAME="CN-CLIP/ViT-L-14-336" DEFAULT_REPLICAS=1 # 副本数 # 项目路径 diff --git a/scripts/stop_clip_api.sh b/scripts/stop_clip_api.sh deleted file mode 100755 index 128d52d..0000000 --- a/scripts/stop_clip_api.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -############################################################################### -# CN-CLIP REST API 停止脚本 -# -# 用途: -# 停止 REST API 服务 -# -# 使用方法: -# ./scripts/stop_clip_api.sh -# -############################################################################### - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# 项目路径 -PROJECT_ROOT="/data/tw/SearchEngine" -PID_FILE="${PROJECT_ROOT}/logs/clip_rest_api.pid" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}停止 CN-CLIP REST API 服务${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# 检查 PID 文件 -if [ ! -f "${PID_FILE}" ]; then - echo -e "${YELLOW}警告: 未找到 PID 文件${NC}" - echo -e "${YELLOW}REST API 服务可能未运行${NC}" - - # 尝试查找并终止进程 - if ps aux | grep "clip_rest_api.py" | grep -v grep > /dev/null; then - echo -e "${YELLOW}发现运行中的 API 进程${NC}" - API_PIDS=$(ps aux | grep "clip_rest_api.py" | grep -v grep | awk '{print $2}') - echo -e "${YELLOW}正在终止...${NC}" - for PID in ${API_PIDS}; do - kill ${PID} 2>/dev/null || true - done - sleep 1 - echo -e "${GREEN}✓ 进程已终止${NC}" - fi - exit 0 -fi - -# 读取 PID -PID="$(cat "${PID_FILE}")" - -# 检查进程 -if ps -p "${PID}" > /dev/null 2>&1; then - echo -e "${BLUE}服务信息:${NC}" - echo " PID: ${PID}" - echo "" - - echo -e "${YELLOW}正在停止服务...${NC}" - kill "${PID}" || true - sleep 2 - - # 检查是否还在运行 - if ps -p "${PID}" > /dev/null 2>&1; then - echo -e "${YELLOW}进程仍在运行,强制终止...${NC}" - kill -9 "${PID}" || true - sleep 1 - fi - - # 最终检查 - if ps -p "${PID}" > /dev/null 2>&1; then - echo -e "${RED}错误: 无法停止进程${NC}" - exit 1 - else - echo -e "${GREEN}========================================${NC}" - echo -e "${GREEN}✓ REST API 服务已停止${NC}" - echo -e "${GREEN}========================================${NC}" - fi -else - echo -e "${YELLOW}警告: 进程 ${PID} 不存在${NC}" -fi - -# 清理 PID 文件 -rm -f "${PID_FILE}" -echo "" -echo -e "${GREEN}PID 文件已删除${NC}" diff --git a/scripts/stop_cnclip_service.sh b/scripts/stop_cnclip_service.sh index f33b988..0a01344 100755 --- a/scripts/stop_cnclip_service.sh +++ b/scripts/stop_cnclip_service.sh @@ -89,21 +89,42 @@ echo "" echo -e "${YELLOW}正在停止服务...${NC}" -# 发送 SIGTERM 信号 -kill "${PID}" || true -sleep 2 - -# 检查进程是否还在运行 -if ps -p "${PID}" > /dev/null 2>&1; then - echo -e "${YELLOW}进程仍在运行,发送 SIGKILL...${NC}" - kill -9 "${PID}" || true +# 查找所有相关的 clip_server 进程(通过配置文件路径) +CONFIG_FILE="torch-flow-temp.yml" +ALL_PIDS=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | awk '{print $2}') + +if [ -z "${ALL_PIDS}" ]; then + # 如果没有找到,至少尝试停止 PID 文件中的进程 + ALL_PIDS="${PID}" +fi + +# 终止所有相关进程 +for P in ${ALL_PIDS}; do + if ps -p "${P}" > /dev/null 2>&1; then + echo -e "${YELLOW}终止进程 ${P}...${NC}" + kill "${P}" 2>/dev/null || true + fi +done + +# 等待进程退出 +sleep 3 + +# 检查是否还有进程在运行,如果有则强制终止 +REMAINING_PIDS=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | awk '{print $2}') +if [ -n "${REMAINING_PIDS}" ]; then + echo -e "${YELLOW}发现仍在运行的进程,强制终止...${NC}" + for P in ${REMAINING_PIDS}; do + echo -e "${YELLOW}强制终止进程 ${P}...${NC}" + kill -9 "${P}" 2>/dev/null || true + done sleep 1 fi # 最终检查 -if ps -p "${PID}" > /dev/null 2>&1; then - echo -e "${RED}错误: 无法停止进程 ${PID}${NC}" - echo -e "${YELLOW}请手动停止: kill -9 ${PID}${NC}" +FINAL_CHECK=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | wc -l) +if [ "${FINAL_CHECK}" -gt 0 ]; then + echo -e "${RED}错误: 仍有进程无法停止${NC}" + echo -e "${YELLOW}请手动检查: ps aux | grep clip_server${NC}" exit 1 else echo -e "${GREEN}========================================${NC}" diff --git a/scripts/test_cnclip_client.py b/scripts/test_cnclip_client.py deleted file mode 100755 index b633bf7..0000000 --- a/scripts/test_cnclip_client.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -""" -CN-CLIP 服务客户端测试脚本 - -用法: - python scripts/test_cnclip_client.py [--url URL] - -注意:如果服务配置了 protocol: http,必须使用 http:// 而不是 grpc:// -""" - -import sys -import argparse -from pathlib import Path - -# 添加项目路径 -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) - -try: - from clip_client import Client -except ImportError: - print("错误: 请先安装 clip-client: pip install clip-client") - sys.exit(1) - - -def test_text_encoding(client): - """测试文本编码""" - print("\n测试文本编码...") - try: - texts = ['这是测试文本', '另一个测试文本'] - result = client.encode(texts) - print(f"✓ 成功! 形状: {result.shape}") - print(f" 输入: {len(texts)} 个文本") - print(f" 输出维度: {result.shape[1]}") - return True - except Exception as e: - print(f"✗ 失败: {e}") - return False - - -def test_image_encoding(client): - """测试图像编码""" - print("\n测试图像编码...") - try: - images = ['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg'] - result = client.encode(images) - print(f"✓ 成功! 形状: {result.shape}") - print(f" 输入: {len(images)} 个图像") - print(f" 输出维度: {result.shape[1]}") - return True - except Exception as e: - print(f"✗ 失败: {e}") - print(" 注意: CN-CLIP 的图像编码可能存在兼容性问题") - return False - - -def main(): - parser = argparse.ArgumentParser(description='CN-CLIP 服务客户端测试') - parser.add_argument( - '--url', - default='http://localhost:51000', - help='服务地址(默认: http://localhost:51000)' - ) - - args = parser.parse_args() - - print("=" * 50) - print("CN-CLIP 服务客户端测试") - print("=" * 50) - print(f"服务地址: {args.url}") - - # 检查协议 - if args.url.startswith('grpc://'): - print("\n⚠ 警告: 服务配置了 protocol: http,请使用 http:// 而不是 grpc://") - print(" 将自动转换为 http://") - args.url = args.url.replace('grpc://', 'http://') - print(f" 新地址: {args.url}") - - try: - client = Client(args.url) - print("✓ 客户端创建成功") - except Exception as e: - print(f"✗ 客户端创建失败: {e}") - sys.exit(1) - - # 运行测试 - results = [] - results.append(test_text_encoding(client)) - results.append(test_image_encoding(client)) - - # 汇总 - print("\n" + "=" * 50) - print("测试结果汇总") - print("=" * 50) - print(f"总测试数: {len(results)}") - print(f"通过: {sum(results)}") - print(f"失败: {len(results) - sum(results)}") - - if all(results): - print("\n✓ 所有测试通过!") - sys.exit(0) - else: - print("\n✗ 部分测试失败") - sys.exit(1) - - -if __name__ == '__main__': - main() - diff --git a/scripts/test_cnclip_service.py b/scripts/test_cnclip_service.py deleted file mode 100755 index 9bfbbfe..0000000 --- a/scripts/test_cnclip_service.py +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env python3 -""" -CN-CLIP 服务测试脚本 - -用法: - python scripts/test_cnclip_service.py - -选项: - --url TEXT 服务地址(默认:grpc://localhost:51000) - --text 只测试文本编码 - --image 只测试图像编码 - --batch-size INT 批处理大小(默认:10) - --help 显示帮助信息 -""" - -import sys -import time -import argparse -from pathlib import Path - -# 添加项目路径到 sys.path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) - -# 颜色输出 -class Colors: - GREEN = '\033[0;32m' - RED = '\033[0;31m' - YELLOW = '\033[1;33m' - BLUE = '\033[0;34m' - NC = '\033[0m' - - -def print_success(msg): - print(f"{Colors.GREEN}✓ {msg}{Colors.NC}") - - -def print_error(msg): - print(f"{Colors.RED}✗ {msg}{Colors.NC}") - - -def print_warning(msg): - print(f"{Colors.YELLOW}⚠ {msg}{Colors.NC}") - - -def print_info(msg): - print(f"{Colors.BLUE}ℹ {msg}{Colors.NC}") - - -def test_imports(): - """测试必要的依赖是否安装""" - print("\n" + "="*50) - print("测试 1: 检查依赖") - print("="*50) - - try: - import clip_client - print_success("clip_client 已安装") - except ImportError as e: - print_error(f"clip_client 未安装: {e}") - print_info("请运行: pip install clip-client") - return False - - try: - import numpy as np - print_success("numpy 已安装") - except ImportError as e: - print_error(f"numpy 未安装: {e}") - return False - - return True - - -def test_connection(url): - """测试服务连接""" - print("\n" + "="*50) - print("测试 2: 连接服务") - print("="*50) - print(f"服务地址: {url}") - - try: - from clip_client import Client - - client = Client(url) - print_success("客户端创建成功") - return client - except Exception as e: - print_error(f"连接失败: {e}") - print_info("请确保服务已启动: ./scripts/start_cnclip_service.sh") - return None - - -def test_text_encoding(client, batch_size=10): - """测试文本编码""" - print("\n" + "="*50) - print("测试 3: 文本编码") - print("="*50) - - try: - # 准备测试数据 - test_texts = [ - '你好,世界', - 'CN-CLIP 图像编码服务', - '这是一个测试', - '人工智能', - '机器学习', - '深度学习', - '计算机视觉', - '自然语言处理', - '搜索引擎', - '多模态检索', - ][:batch_size] - - print(f"测试文本数量: {len(test_texts)}") - print(f"示例文本: {test_texts[0]}") - - # 执行编码 - start_time = time.time() - embeddings = client.encode(test_texts) - elapsed_time = time.time() - start_time - - # 验证结果 - assert embeddings.shape[0] == len(test_texts), "向量数量不匹配" - assert embeddings.shape[1] == 1024, "向量维度应该是 1024" - - print_success(f"编码成功") - print(f" 向量形状: {embeddings.shape}") - print(f" 耗时: {elapsed_time:.2f}秒") - print(f" 速度: {len(test_texts)/elapsed_time:.2f} 条/秒") - print(f" 数据类型: {embeddings.dtype}") - - return True - - except Exception as e: - print_error(f"文本编码失败: {e}") - return False - - -def test_image_encoding(client, batch_size=5): - """测试图像编码""" - print("\n" + "="*50) - print("测试 4: 图像编码") - print("="*50) - - try: - # 准备测试数据(使用在线图片) - test_images = [ - 'https://picsum.photos/224', - 'https://picsum.photos/224?random=1', - 'https://picsum.photos/224?random=2', - 'https://picsum.photos/224?random=3', - 'https://picsum.photos/224?random=4', - ][:batch_size] - - print(f"测试图像数量: {len(test_images)}") - print(f"示例 URL: {test_images[0]}") - - # 执行编码 - start_time = time.time() - embeddings = client.encode(test_images) - elapsed_time = time.time() - start_time - - # 验证结果 - assert embeddings.shape[0] == len(test_images), "向量数量不匹配" - assert embeddings.shape[1] == 1024, "向量维度应该是 1024" - - print_success(f"编码成功") - print(f" 向量形状: {embeddings.shape}") - print(f" 耗时: {elapsed_time:.2f}秒") - print(f" 速度: {len(test_images)/elapsed_time:.2f} 条/秒") - print(f" 数据类型: {embeddings.dtype}") - - return True - - except Exception as e: - print_error(f"图像编码失败: {e}") - print_warning("可能需要网络连接来下载测试图片") - return False - - -def test_mixed_encoding(client): - """测试混合编码(文本+图像)""" - print("\n" + "="*50) - print("测试 5: 混合编码") - print("="*50) - - try: - # 准备混合数据 - mixed_data = [ - '这是一段测试文本', - 'https://picsum.photos/224?random=10', - 'CN-CLIP 图像编码', - 'https://picsum.photos/224?random=11', - ] - - print(f"混合数据数量: {len(mixed_data)}") - print(f" 文本: 2 条") - print(f" 图像: 2 条") - - # 执行编码 - start_time = time.time() - embeddings = client.encode(mixed_data) - elapsed_time = time.time() - start_time - - # 验证结果 - assert embeddings.shape[0] == len(mixed_data), "向量数量不匹配" - assert embeddings.shape[1] == 1024, "向量维度应该是 1024" - - print_success(f"混合编码成功") - print(f" 向量形状: {embeddings.shape}") - print(f" 耗时: {elapsed_time:.2f}秒") - - return True - - except Exception as e: - print_error(f"混合编码失败: {e}") - return False - - -def test_single_encoding(client): - """测试单个数据编码""" - print("\n" + "="*50) - print("测试 6: 单个数据编码") - print("="*50) - - try: - # 测试单个文本 - single_text = '测试文本' - print(f"输入: {single_text}") - - start_time = time.time() - embedding = client.encode(single_text) - elapsed_time = time.time() - start_time - - # 注意:单个数据会返回 (1, 1024) 的形状 - if embedding.ndim == 1: - embedding = embedding.reshape(1, -1) - - assert embedding.shape == (1, 1024), f"向量形状应该是 (1, 1024), 实际是 {embedding.shape}" - - print_success(f"单个文本编码成功") - print(f" 向量形状: {embedding.shape}") - print(f" 耗时: {elapsed_time:.2f}秒") - - return True - - except Exception as e: - print_error(f"单个数据编码失败: {e}") - return False - - -def main(): - parser = argparse.ArgumentParser(description='CN-CLIP 服务测试脚本') - parser.add_argument('--url', - default='grpc://localhost:51000', - help='服务地址(默认:grpc://localhost:51000)') - parser.add_argument('--text', - action='store_true', - help='只测试文本编码') - parser.add_argument('--image', - action='store_true', - help='只测试图像编码') - parser.add_argument('--batch-size', - type=int, - default=10, - help='批处理大小(默认:10)') - - args = parser.parse_args() - - print("\n" + "="*50) - print("CN-CLIP 服务测试") - print("="*50) - - # 测试 1: 检查依赖 - if not test_imports(): - sys.exit(1) - - # 测试 2: 连接服务 - client = test_connection(args.url) - if not client: - sys.exit(1) - - # 运行测试 - results = [] - - if args.text: - # 只测试文本编码 - results.append(test_text_encoding(client, args.batch_size)) - elif args.image: - # 只测试图像编码 - results.append(test_image_encoding(client, args.batch_size)) - else: - # 运行所有测试 - results.append(test_text_encoding(client, args.batch_size)) - results.append(test_image_encoding(client, min(args.batch_size, 5))) - results.append(test_mixed_encoding(client)) - results.append(test_single_encoding(client)) - - # 汇总结果 - print("\n" + "="*50) - print("测试结果汇总") - print("="*50) - - total_tests = len(results) - passed_tests = sum(results) - - print(f"总测试数: {total_tests}") - print(f"通过: {passed_tests}") - print(f"失败: {total_tests - passed_tests}") - - if passed_tests == total_tests: - print_success("\n所有测试通过!") - sys.exit(0) - else: - print_error("\n部分测试失败") - sys.exit(1) - - -if __name__ == '__main__': - main() -- libgit2 0.21.2