Commit 74cca19021605ac3bc84f175293768d0b09ec7b4

Authored by tangwang
1 parent 40f1e391

cnclip

docs/CLIP_REPLICAS_GUIDE.md deleted
... ... @@ -1,415 +0,0 @@
1   -# CN-CLIP 服务副本数配置指南
2   -
3   -## 📊 副本数(Replicas)详解
4   -
5   -### 什么是副本?
6   -
7   -**副本(Replicas)** 是指同时运行的模型实例数量。每个副本都是独立的进程,可以:
8   -- 并行处理多个请求
9   -- 提高服务吞吐量
10   -- 充分利用多核 CPU 或多 GPU
11   -
12   -**关键点**:
13   -- ✅ 每个副本加载一份完整的模型到内存/显存
14   -- ✅ 副本间共享网络端口(通过负载均衡)
15   -- ✅ 多副本**不会**自动分配到不同 GPU(需手动配置)
16   -
17   ----
18   -
19   -## 💾 显存占用分析
20   -
21   -### 问题:2个副本会比1个副本多一倍显存吗?
22   -
23   -**答案:是的!基本成倍增加。**
24   -
25   -| 副本数 | 模型显存 | 峰值显存 | 总显存占用 |
26   -|--------|---------|---------|-----------|
27   -| 1副本 | ~2-3 GB | ~4-5 GB | **~4-5 GB** |
28   -| 2副本 | ~4-6 GB | ~8-10 GB | **~8-10 GB** |
29   -| 3副本 | ~6-9 GB | ~12-15 GB | **~12-15 GB** |
30   -| 4副本 | ~8-12 GB | ~16-20 GB | **~16-20 GB** |
31   -
32   -**说明**:
33   -- **模型显存**:模型权重(固定部分)
34   -- **峰值显存**:模型 + 激活值 + 中间结果(批处理时)
35   -- **CN-CLIP/ViT-H-14**:约 2-3 GB 模型权重
36   -
37   -### 你的 GPU 情况
38   -
39   -```
40   -GPU: Tesla T4
41   -总显存: 16384 MiB (16 GB)
42   -当前空闲: ~2 GB
43   -```
44   -
45   -**推荐配置**:
46   -- **1副本**:最安全,适合当前场景
47   -- **2副本**:需要先停止其他占用显存的程序
48   -- **3+副本**:不建议(显存不足)
49   -
50   ----
51   -
52   -## ⚙️ 配置副本数
53   -
54   -### 方法 1:修改配置文件(推荐)
55   -
56   -编辑 `third-party/clip-as-service/server/clip_server/torch-flow.yml`:
57   -
58   -```yaml
59   -jtype: Flow
60   -version: '1'
61   -with:
62   - port: 51000
63   -executors:
64   - - name: clip_t
65   - uses:
66   - jtype: CLIPEncoder
67   - with:
68   - name: 'CN-CLIP/ViT-H-14'
69   - device: 'cuda' # 设备
70   - minibatch_size: 32 # 批处理大小
71   - num_worker_preprocess: 4 # 预处理线程
72   - dtype: 'float16' # 数据类型
73   - metas:
74   - py_modules:
75   - - clip_server.executors.clip_torch
76   - timeout_ready: 3000000
77   - replicas: 2 # ← 修改这里(1, 2, 3...)
78   -```
79   -
80   -### 方法 2:命令行参数
81   -
82   -```bash
83   -# 启动时指定副本数(需要修改启动脚本)
84   -python -m clip_server \
85   - --name 'CN-CLIP/ViT-H-14' \
86   - --replicas 2 \
87   - --port 51000
88   -```
89   -
90   -### 方法 3:使用改进的启动脚本
91   -
92   -我会在下面提供一个支持副本数的启动脚本。
93   -
94   ----
95   -
96   -## 📈 性能对比
97   -
98   -### 吞吐量测试(理论值)
99   -
100   -**配置**:CN-CLIP/ViT-H-14, Tesla T4, batch_size=32
101   -
102   -| 副本数 | QPS (请求/秒) | 显存占用 | 推荐场景 |
103   -|--------|--------------|---------|---------|
104   -| 1 | ~80-100 | ~4-5 GB | 开发测试、低并发 |
105   -| 2 | ~150-180 | ~8-10 GB | 生产环境、中等并发 |
106   -| 3 | ~220-260 | ~12-15 GB | 高并发(需24GB+显存)|
107   -| 4 | ~300-350 | ~16-20 GB | 极高并发(需A100等)|
108   -
109   -**注意**:
110   -- 实际性能取决于批处理大小、请求类型(文本/图像)
111   -- 并发请求少时,多副本提升不明显
112   -- 并发请求多时,多副本显著提升吞吐量
113   -
114   -### 延迟对比
115   -
116   -| 副本数 | 单请求延迟 | P99 延迟 | 说明 |
117   -|--------|-----------|---------|------|
118   -| 1 | ~50ms | ~200ms | 无等待,直接处理 |
119   -| 2 | ~50ms | ~150ms | 有等待,但处理快 |
120   -| 4 | ~50ms | ~100ms | 等待少,处理很快 |
121   -
122   -**结论**:
123   -- ✅ 多副本**不降低**单请求延迟
124   -- ✅ 多副本**显著降低** P99 延迟(高并发时)
125   -- ✅ 多副本**显著提升**吞吐量
126   -
127   ----
128   -
129   -## 🎯 配置建议
130   -
131   -### 场景 1:开发/测试(推荐)
132   -
133   -```yaml
134   -replicas: 1
135   -minibatch_size: 32
136   -device: cuda # 或 cpu
137   -```
138   -
139   -**特点**:
140   -- ✅ 显存占用低(~5GB)
141   -- ✅ 足够测试使用
142   -- ✅ 启动快
143   -
144   -### 场景 2:生产环境(中等并发)
145   -
146   -```yaml
147   -replicas: 2
148   -minibatch_size: 32
149   -device: cuda
150   -```
151   -
152   -**前提条件**:
153   -- ✅ GPU 显存 ≥ 12GB
154   -- ✅ 没有其他显存占用程序
155   -- ✅ 需要 ~10GB 显存
156   -
157   -**特点**:
158   -- ✅ 吞吐量翻倍(~150-180 QPS)
159   -- ✅ 处理并发请求能力强
160   -- ⚠️ 需要确保显存充足
161   -
162   -### 场景 3:生产环境(高并发)
163   -
164   -```yaml
165   -replicas: 2
166   -minibatch_size: 64
167   -device: cuda
168   -```
169   -
170   -**优化点**:
171   -- ✅ 增大批处理(利用多副本优势)
172   -- ✅ 吞吐量进一步提升
173   -- ⚠️ 需要更多显存(~12-15GB)
174   -
175   -### 场景 4:多 GPU 服务器
176   -
177   -如果你有多块 GPU:
178   -
179   -```yaml
180   -executors:
181   - - name: clip_t_gpu0
182   - uses:
183   - jtype: CLIPEncoder
184   - with:
185   - device: 'cuda:0'
186   - replicas: 2
187   - uses:
188   - jtype: CLIPEncoder
189   - with:
190   - device: 'cuda:1'
191   - replicas: 2
192   -```
193   -
194   -**说明**:
195   -- 2块GPU,每块2个副本 = 共4个副本
196   -- 总吞吐量 ~300+ QPS
197   -- 每块GPU占用 ~8-10GB
198   -
199   ----
200   -
201   -## 🔍 监控和调试
202   -
203   -### 查看显存占用
204   -
205   -```bash
206   -# 实时监控
207   -watch -n 1 nvidia-smi
208   -
209   -# 查看详细信息
210   -nvidia-smi --query-gpu=timestamp,name,memory.used,memory.free,utilization.gpu --format=csv
211   -
212   -# 持续监控(每秒刷新,共100次)
213   -nvidia-smi dmon -s u -c 100
214   -```
215   -
216   -### 查看副本进程
217   -
218   -```bash
219   -# 查看所有 clip_server 进程
220   -ps aux | grep clip_server
221   -
222   -# 应该看到:
223   -# root 12345 clip_server (main)
224   -# root 12346 clip_server (replica 1)
225   -# root 12347 clip_server (replica 2)
226   -# ...
227   -
228   -# 查看进程数
229   -ps aux | grep clip_server | wc -l
230   -
231   -# 应该 = replicas + 1
232   -```
233   -
234   -### 测试吞吐量
235   -
236   -```bash
237   -# 使用测试脚本
238   -python scripts/test_cnclip_service.py --batch-size 100
239   -
240   -# 观察日志
241   -tail -f logs/cnclip_service.log | grep "encoded"
242   -```
243   -
244   ----
245   -
246   -## ⚠️ 常见问题
247   -
248   -### Q1: 设置2副本后显存不足 (OOM)
249   -
250   -**错误信息**:
251   -```
252   -RuntimeError: CUDA out of memory. Tried to allocate XXX MiB
253   -```
254   -
255   -**解决方案**:
256   -
257   -**方案 A**:减小批处理大小
258   -```yaml
259   -replicas: 2
260   -minibatch_size: 16 # 从32减到16
261   -```
262   -
263   -**方案 B**:使用更小的模型
264   -```yaml
265   -name: 'CN-CLIP/ViT-L-14' # 从 H-14 改为 L-14
266   -replicas: 2
267   -```
268   -
269   -**方案 C**:减少副本数
270   -```yaml
271   -replicas: 1 # 回退到单副本
272   -```
273   -
274   -### Q2: 多副本反而性能下降
275   -
276   -**原因**:
277   -- GPU 资源竞争(显存带宽、计算单元)
278   -- 批处理太小(未充分利用并行)
279   -- CPU 预处理成为瓶颈
280   -
281   -**解决方案**:
282   -```yaml
283   -# 增大批处理
284   -minibatch_size: 64
285   -
286   -# 增加预处理线程
287   -num_worker_preprocess: 8
288   -
289   -# 或减少副本
290   -replicas: 1
291   -```
292   -
293   -### Q3: 如何知道最佳副本数?
294   -
295   -**实验方法**:
296   -
297   -```bash
298   -# 测试脚本
299   -for replicas in 1 2 3; do
300   - echo "Testing $replicas replicas..."
301   -
302   - # 修改配置
303   - sed -i "s/replicas: .*/replicas: $replicas/" torch-flow.yml
304   -
305   - # 重启服务
306   - ./scripts/stop_cnclip_service.sh
307   - ./scripts/start_cnclip_service.sh
308   -
309   - # 等待启动
310   - sleep 30
311   -
312   - # 运行测试
313   - python scripts/test_cnclip_service.py --batch-size 100
314   -done
315   -
316   -# 对比结果,选择最优配置
317   -```
318   -
319   -**推荐配置**:
320   -- **Tesla T4 (16GB)**: 1-2 副本
321   -- **RTX 3090 (24GB)**: 2-3 副本
322   -- **A100 (40GB)**: 3-4 副本
323   -
324   ----
325   -
326   -## 📝 快速配置参考
327   -
328   -### 修改启动脚本支持副本数
329   -
330   -编辑 `scripts/start_cnclip_service.sh`,添加参数:
331   -
332   -```bash
333   -# 在参数解析部分添加
334   ---replicas)
335   - REPLICAS="$2"
336   - shift 2
337   - ;;
338   -
339   -# 在启动命令中使用
340   -nohup python -m clip_server \
341   - --name "${MODEL_NAME}" \
342   - --replicas ${REPLICAS:-1} \ # ← 添加这一行
343   - --port ${PORT} \
344   - ...
345   -```
346   -
347   -### 当前最佳实践(Tesla T4)
348   -
349   -**保守配置**(推荐):
350   -```yaml
351   -replicas: 1
352   -minibatch_size: 32
353   -dtype: float16
354   -```
355   -- 显存:~5GB
356   -- QPS:~80-100
357   -- 适合:开发、测试、小规模应用
358   -
359   -**激进配置**(需测试):
360   -```yaml
361   -replicas: 2
362   -minibatch_size: 32
363   -dtype: float16
364   -```
365   -- 显存:~10GB
366   -- QPS:~150-180
367   -- 适合:生产环境、中等并发
368   -- 前提:清理其他显存占用
369   -
370   ----
371   -
372   -## 🎉 总结
373   -
374   -### 关键要点
375   -
376   -1. **显存占用**:副本数 × 单副本显存(基本线性增长)
377   -2. **性能提升**:吞吐量接近线性增长,但不完美
378   -3. **推荐配置**:
379   - - Tesla T4 (16GB): **1-2 副本**
380   - - 确保显存充足 + 预留 20% 余量
381   -4. **优化建议**:
382   - - 从小副本开始测试
383   - - 根据实际负载调整
384   - - 监控显存和性能指标
385   -
386   -### 你的情况
387   -
388   -**当前配置**:
389   -```yaml
390   -replicas: 1 # 当前配置
391   -```
392   -
393   -**建议**:
394   -- ✅ 先保持 `replicas: 1`,测试性能
395   -- ✅ 如果吞吐量不够,再尝试 `replicas: 2`
396   -- ⚠️ 确保显存充足(当前空闲2GB可能不够2副本)
397   -- 💡 考虑先清理其他显存占用
398   -
399   -**快速测试**:
400   -```bash
401   -# 1. 查看当前显存占用
402   -nvidia-smi
403   -
404   -# 2. 启动1副本,观察显存
405   -./scripts/start_cnclip_service.sh
406   -nvidia-smi # 应该增加 ~5GB
407   -
408   -# 3. 如果还有空间,尝试2副本
409   -./scripts/stop_cnclip_service.sh
410   -# 修改 torch-flow.yml 中的 replicas: 2
411   -./scripts/start_cnclip_service.sh
412   -nvidia-smi # 应该再增加 ~5GB
413   -```
414   -
415   -需要我帮你修改启动脚本以支持副本数参数吗?
docs/CNCLIP_SERVICE.md
1   -# CN-CLIP 编码服务使用指南
  1 +# CN-CLIP 编码服务
2 2  
3   -## 简介
  3 +## 模块说明
4 4  
5   -本服务基于 [clip-as-service](https://github.com/jina-ai/clip-as-service) 提供 CN-CLIP 模型的文本和图像编码功能。
  5 +CN-CLIP 编码服务基于 [clip-as-service](https://github.com/jina-ai/clip-as-service) 提供中文 CLIP 模型的文本和图像编码功能。服务使用 gRPC 协议,支持批量编码,返回固定维度的向量表示。
  6 +
  7 +### 功能特性
  8 +
  9 +- 文本编码:将中文文本编码为向量
  10 +- 图像编码:将图像(本地文件或远程 URL)编码为向量
  11 +- 混合编码:同时编码文本和图像
  12 +- 批量处理:支持批量编码,提高效率
  13 +
  14 +### 技术架构
  15 +
  16 +- **框架**: clip-as-service (基于 Jina)
  17 +- **模型**: CN-CLIP/ViT-L-14-336(默认)
  18 +- **协议**: gRPC(默认,官方推荐)
  19 +- **运行时**: PyTorch
6 20  
7 21 ## 启动服务
8 22  
  23 +### 基本用法
  24 +
9 25 ```bash
10 26 ./scripts/start_cnclip_service.sh
11 27 ```
12 28  
13 29 ### 启动参数
14 30  
15   -- `--port PORT`: 服务端口(默认:51000)
16   -- `--device DEVICE`: 设备类型:cuda 或 cpu(默认:自动检测)
17   -- `--batch-size SIZE`: 批处理大小(默认:32)
18   -- `--num-workers NUM`: 预处理线程数(默认:4)
19   -- `--dtype TYPE`: 数据类型:float16 或 float32(默认:float16)
20   -- `--model-name NAME`: 模型名称(默认:CN-CLIP/ViT-H-14)
21   -- `--replicas NUM`: 副本数(默认:1)
  31 +| 参数 | 说明 | 默认值 |
  32 +|------|------|--------|
  33 +| `--port PORT` | 服务端口 | 51000 |
  34 +| `--device DEVICE` | 设备类型:cuda 或 cpu | 自动检测 |
  35 +| `--batch-size SIZE` | 批处理大小 | 32 |
  36 +| `--num-workers NUM` | 预处理线程数 | 4 |
  37 +| `--dtype TYPE` | 数据类型:float16 或 float32 | float16 |
  38 +| `--model-name NAME` | 模型名称 | CN-CLIP/ViT-L-14-336 |
  39 +| `--replicas NUM` | 副本数 | 1 |
22 40  
23 41 ### 示例
24 42  
... ... @@ -29,133 +47,102 @@
29 47 # 指定端口和设备
30 48 ./scripts/start_cnclip_service.sh --port 52000 --device cpu
31 49  
32   -# 调整批处理大小
33   -./scripts/start_cnclip_service.sh --batch-size 16 --dtype float32
  50 +# 使用其他模型
  51 +./scripts/start_cnclip_service.sh --model-name CN-CLIP/ViT-H-14
34 52 ```
35 53  
36   -## 停止服务
  54 +### 停止服务
37 55  
38 56 ```bash
39 57 ./scripts/stop_cnclip_service.sh
40 58 ```
41 59  
42   -## 使用 API
  60 +## API 接口说明
43 61  
44   -### 编码文本
  62 +### Python 客户端
45 63  
46   -```bash
47   -curl -X POST http://localhost:51000/post \
48   - -H 'Content-Type: application/json' \
49   - -d '{
50   - "data": [
51   - {"text": "这是一个测试文本"},
52   - {"text": "另一个文本"}
53   - ],
54   - "execEndpoint": "/"
55   - }'
56   -```
  64 +服务使用 gRPC 协议,必须使用 Python 客户端:
57 65  
58   -### 编码图像(远程 URL)
  66 +```python
  67 +from clip_client import Client
59 68  
60   -```bash
61   -curl -X POST http://localhost:51000/post \
62   - -H 'Content-Type: application/json' \
63   - -d '{
64   - "data": [
65   - {"uri": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"}
66   - ],
67   - "execEndpoint": "/"
68   - }'
  69 +# 创建客户端(使用 grpc:// 协议)
  70 +c = Client('grpc://localhost:51000')
69 71 ```
70 72  
71   -### 编码图像(本地文件,base64)
  73 +### 编码接口
72 74  
73   -```bash
74   -curl -X POST http://localhost:51000/post \
75   - -H 'Content-Type: application/json' \
76   - -d "{
77   - \"data\": [
78   - {\"blob\": \"$(base64 -w 0 /path/to/image.jpg)\"}
79   - ],
80   - \"execEndpoint\": \"/\"
81   - }"
82   -```
  75 +#### 1. 文本编码
83 76  
84   -### 混合编码(文本和图像)
  77 +```python
  78 +from clip_client import Client
85 79  
86   -```bash
87   -curl -X POST http://localhost:51000/post \
88   - -H 'Content-Type: application/json' \
89   - -d '{
90   - "data": [
91   - {"text": "这是一段文本"},
92   - {"uri": "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"}
93   - ],
94   - "execEndpoint": "/"
95   - }'
96   -```
  80 +c = Client('grpc://localhost:51000')
  81 +
  82 +# 编码单个文本
  83 +result = c.encode(['这是测试文本'])
  84 +print(result.shape) # (1, 1024)
97 85  
98   -## 响应格式
99   -
100   -响应为 JSON 格式,编码结果在 `data[].embedding` 字段中:
101   -
102   -```json
103   -{
104   - "header": {...},
105   - "data": [
106   - {
107   - "id": "...",
108   - "text": "这是一个测试文本",
109   - "embedding": [0.123, -0.456, ...]
110   - }
111   - ]
112   -}
  86 +# 编码多个文本
  87 +result = c.encode(['文本1', '文本2', '文本3'])
  88 +print(result.shape) # (3, 1024)
113 89 ```
114 90  
115   -### 提取 embedding
  91 +#### 2. 图像编码
116 92  
117   -使用 `jq` 提取 embedding:
  93 +```python
  94 +# 编码远程图像 URL
  95 +result = c.encode(['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg'])
  96 +print(result.shape) # (1, 1024)
118 97  
119   -```bash
120   -curl -X POST http://localhost:51000/post \
121   - -H 'Content-Type: application/json' \
122   - -d '{"data":[{"text": "测试"}], "execEndpoint":"/"}' | \
123   - jq -c '.data[] | .embedding'
  98 +# 编码本地图像文件
  99 +result = c.encode(['/path/to/image.jpg'])
  100 +print(result.shape) # (1, 1024)
124 101 ```
125 102  
126   -## Python 客户端示例
127   -
128   -**重要**:如果服务配置了 `protocol: http`,客户端必须使用 `http://` 而不是 `grpc://`。
  103 +#### 3. 混合编码
129 104  
130 105 ```python
131   -from clip_client import Client
  106 +# 同时编码文本和图像
  107 +result = c.encode([
  108 + '这是文本',
  109 + 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg',
  110 + '另一个文本'
  111 +])
  112 +print(result.shape) # (3, 1024)
  113 +```
132 114  
133   -# 创建客户端(注意:使用 http:// 而不是 grpc://)
134   -c = Client('http://localhost:51000')
  115 +### 返回格式
135 116  
136   -# 编码文本
137   -result = c.encode(['这是测试文本', '另一个文本'])
138   -print(result.shape) # [2, 768] 或其他维度
  117 +- **类型**: `numpy.ndarray`
  118 +- **形状**: `(N, 1024)`,其中 N 是输入数量
  119 +- **数据类型**: `float32`
  120 +- **维度**: 1024(CN-CLIP 模型的 embedding 维度)
139 121  
140   -# 编码图像
141   -result = c.encode(['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg'])
142   -print(result.shape) # [1, 768]
  122 +### 支持的模型
143 123  
144   -# 混合编码
145   -result = c.encode([
146   - '这是文本',
147   - 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg'
148   -])
149   -print(result.shape) # [2, 768]
  124 +| 模型名称 | 说明 | 推荐场景 |
  125 +|---------|------|---------|
  126 +| `CN-CLIP/ViT-B-16` | 基础版本,速度快 | 对速度要求高的场景 |
  127 +| `CN-CLIP/ViT-L-14` | 平衡版本 | 通用场景 |
  128 +| `CN-CLIP/ViT-L-14-336` | 高分辨率版本(默认) | 需要处理高分辨率图像 |
  129 +| `CN-CLIP/ViT-H-14` | 大型版本,精度高 | 对精度要求高的场景 |
  130 +| `CN-CLIP/RN50` | ResNet-50 版本 | 兼容性场景 |
  131 +
  132 +## 测试
  133 +
  134 +运行测试脚本:
  135 +
  136 +```bash
  137 +./scripts/test_cnclip_service.sh
150 138 ```
151 139  
152   -## 支持的模型
  140 +测试脚本会验证:
  141 +- 文本编码功能
  142 +- 图像编码功能(远程 URL)
  143 +- 混合编码功能
153 144  
154   -- `CN-CLIP/ViT-B-16`: 基础版本,速度快
155   -- `CN-CLIP/ViT-L-14`: 平衡版本
156   -- `CN-CLIP/ViT-L-14-336`: 高分辨率版本
157   -- `CN-CLIP/ViT-H-14`: 大型版本,精度高(默认)
158   -- `CN-CLIP/RN50`: ResNet-50 版本
  145 +每个测试会显示 embedding 的维度和前 20 个数字。
159 146  
160 147 ## 查看日志
161 148  
... ... @@ -165,36 +152,36 @@ tail -f /data/tw/SearchEngine/logs/cnclip_service.log
165 152  
166 153 ## 常见问题
167 154  
168   -### 服务启动失败
  155 +### 1. 服务启动失败
169 156  
170   -1. 检查端口是否被占用:`lsof -i :51000`
171   -2. 检查 conda 环境是否正确激活
172   -3. 查看日志文件获取详细错误信息
  157 +- 检查端口是否被占用:`lsof -i :51000`
  158 +- 检查 conda 环境是否正确激活
  159 +- 查看日志文件获取详细错误信息
173 160  
174   -### 编码失败
  161 +### 2. 客户端连接失败
175 162  
176   -1. 确保请求格式正确,使用 `/post` 端点
177   -2. 确保 `execEndpoint` 设置为 `"/"`
178   -3. 检查图像 URL 是否可访问
179   -4. 查看服务日志排查错误
180   -
181   -### 协议不匹配
182   -
183   -如果服务配置了 `protocol: http`,客户端必须使用 `http://` 而不是 `grpc://`:
  163 +确保使用正确的协议:
184 164  
185 165 ```python
186   -# 正确
187   -c = Client('http://localhost:51000')
188   -
189   -# 错误(会导致连接失败)
  166 +# 正确:使用 grpc://
190 167 c = Client('grpc://localhost:51000')
  168 +
  169 +# 错误:不要使用 http://
  170 +# c = Client('http://localhost:51000') # 会失败
191 171 ```
192 172  
193   -### 图像编码问题
  173 +### 3. 编码失败
  174 +
  175 +- 检查服务是否正常运行
  176 +- 检查输入格式是否正确
  177 +- 查看服务日志排查错误
  178 +
  179 +### 4. 依赖安装
194 180  
195   -CN-CLIP 模型的图像编码可能存在兼容性问题。如果遇到 `AttributeError: 'str' object has no attribute 'to'` 错误,这可能是 clip-as-service 对 CN-CLIP 图像预处理的支持问题。建议
  181 +确保已安装必要的依赖
196 182  
197   -1. 检查 clip-as-service 和 cn-clip 的版本兼容性
198   -2. 尝试使用本地图像文件而不是远程 URL
199   -3. 查看 [clip-as-service 的 GitHub Issues](https://github.com/jina-ai/clip-as-service/issues) 是否有相关报告
  183 +```bash
  184 +pip install clip-client
  185 +```
200 186  
  187 +服务端依赖会在启动脚本中自动检查。
... ...
docs/GIT_SUBMODULE_GUIDE.md deleted
... ... @@ -1,323 +0,0 @@
1   -# Git 子模块处理指南
2   -
3   -## 问题说明
4   -
5   -当前 `third-party/clip-as-service` 是一个 Git 子模块,指向远程仓库:
6   -- **原始仓库**: https://github.com/jina-ai/clip-as-service.git
7   -- **问题**: 你修改了代码,但不想/无法提交到原仓库
8   -- **目标**: 将修改纳入自己的主项目管理
9   -
10   -## 解决方案
11   -
12   -我们提供三个方案,根据你的需求选择:
13   -
14   -### 方案 A:移除子模块,直接纳入主项目(推荐)
15   -
16   -**适用场景**:
17   -- 你不需要跟随上游更新
18   -- 你想完全控制这些代码
19   -- 你会修改这些代码
20   -
21   -**优点**:
22   -- ✅ 简单直接,完全控制
23   -- ✅ 所有代码在一个仓库中,易于管理
24   -- ✅ 修改可以直接提交到主项目
25   -
26   -**缺点**:
27   -- ❌ 无法方便地获取上游更新
28   -- ❌ 会增加主仓库大小
29   -
30   -**操作步骤**:
31   -
32   -```bash
33   -# 1. 进入主项目目录
34   -cd /data/tw/SearchEngine
35   -
36   -# 2. 移除 Git 对子模块的追踪
37   -git rm --cached third-party/clip-as-service
38   -
39   -# 3. 删除 .gitmodules 中的配置(如果有)
40   -# 编辑 .gitmodules 文件,删除 clip-as-service 相关部分
41   -
42   -# 4. 删除子模块的 Git 仓库
43   -rm -rf third-party/clip-as-service/.git
44   -
45   -# 5. 将子模块目录作为普通目录添加到 Git
46   -git add third-party/clip-as-service/
47   -
48   -# 6. 提交更改
49   -git commit -m "feat: 将 clip-as-service 从子模块转为普通目录"
50   -
51   -# 7. 推送到远程仓库
52   -git push origin main
53   -```
54   -
55   -**后续操作**:
56   -- 之后对 `third-party/clip-as-service` 的任何修改都可以正常提交
57   -- 使用 `git add` 和 `git commit` 就可以了
58   -
59   ----
60   -
61   -### 方案 B:切换到自己的 Fork
62   -
63   -**适用场景**:
64   -- 你需要跟随上游更新
65   -- 你想保持 Git 历史记录
66   -- 你可能向原仓库贡献代码
67   -
68   -**优点**:
69   -- ✅ 可以合并上游更新
70   -- ✅ 保持 Git 追踪
71   -- ✅ 可以参与上游社区
72   -
73   -**缺点**:
74   -- ❌ 需要 Fork 并维护自己的仓库
75   -- ❌ 操作相对复杂
76   -
77   -**操作步骤**:
78   -
79   -```bash
80   -# 1. 在 GitHub 上 Fork 原仓库
81   -# 访问 https://github.com/jina-ai/clip-as-service
82   -# 点击 Fork 按钮,创建你自己的副本
83   -
84   -# 2. 克隆你的 Fork(替换为你的用户名)
85   -# 注意:这里使用 --mirror 保留所有分支和标签
86   -git clone --mirror https://github.com/YOUR_USERNAME/clip-as-service.git
87   -
88   -# 3. 进入主项目目录
89   -cd /data/tw/SearchEngine
90   -
91   -# 4. 更新 .gitmodules 文件
92   -# 编辑 .gitmodules,将 URL 改为你的 Fork:
93   -# [submodule "third-party/clip-as-service"]
94   -# path = third-party/clip-as-service
95   -# url = https://github.com/YOUR_USERNAME/clip-as-service.git
96   -
97   -# 5. 初始化新的子模块
98   -git submodule deinit -f third-party/clip-as-service
99   -git submodule update --init --remote third-party/clip-as-service
100   -
101   -# 6. 进入子模块目录,设置你的 fork 为默认远程
102   -cd third-party/clip-as-service
103   -git remote set-url origin https://github.com/YOUR_USERNAME/clip-as-service.git
104   -git remote add upstream https://github.com/jina-ai/clip-as-service.git
105   -
106   -# 7. 创建并切换到你的分支
107   -git checkout -b custom-cnclip-support
108   -
109   -# 8. 提交你的修改
110   -git add .
111   -git commit -m "feat: 添加 CN-CLIP 自定义配置"
112   -
113   -# 9. 推送到你的 Fork
114   -git push origin custom-cnclip-support
115   -
116   -# 10. 回到主项目,更新子模块引用
117   -cd /data/tw/SearchEngine
118   -git add third-party/clip-as-service
119   -git commit -m "chore: 更新子模块到自定义版本"
120   -git push origin main
121   -```
122   -
123   -**后续操作**:
124   -- 在子模块目录中修改代码
125   -- 提交到你的分支:`git push origin custom-cnclip-support`
126   -- 合并上游更新:
127   - ```bash
128   - cd third-party/clip-as-service
129   - git fetch upstream
130   - git merge upstream/main
131   - git push origin custom-cnclip-support
132   - cd ..
133   - git add third-party/clip-as-service
134   - git commit -m "chore: 合并上游更新"
135   - ```
136   -
137   ----
138   -
139   -### 方案 C:使用 Git Subtree 替代 Submodule
140   -
141   -**适用场景**:
142   -- 你想要子模块的灵活性,但不想处理 submodule 的复杂性
143   -- 你想直接在主项目中管理代码
144   -
145   -**优点**:
146   -- ✅ 比 submodule 更易用
147   -- ✅ 可以直接提交到主项目
148   -- ✅ 可以获取上游更新
149   -
150   -**缺点**:
151   -- ❌ 命令相对复杂
152   -- ❌ 重写现有历史
153   -
154   -**操作步骤**:
155   -
156   -```bash
157   -# 1. 移除现有子模块
158   -cd /data/tw/SearchEngine
159   -git submodule deinit -f third-party/clip-as-service
160   -git rm -f third-party/clip-as-service
161   -rm -rf .git/modules/third-party/clip-as-service
162   -
163   -# 2. 使用 subtree 添加远程仓库
164   -git subtree add --prefix=third-party/clip-as-service \
165   - https://github.com/jina-ai/clip-as-service.git \
166   - main --squash
167   -
168   -# 3. 提交
169   -git commit -m "chore: 使用 git subtree 替代 submodule 添加 clip-as-service"
170   -
171   -# 4. 推送
172   -git push origin main
173   -```
174   -
175   -**后续操作**:
176   -- 修改代码后直接在主项目中提交
177   -- 获取上游更新:
178   - ```bash
179   - git subtree pull --prefix=third-party/clip-as-service \
180   - https://github.com/jina-ai/clip-as-service.git \
181   - main --squash
182   - ```
183   -
184   ----
185   -
186   -## 推荐方案
187   -
188   -根据你的情况,我**推荐方案 A**,原因如下:
189   -
190   -1. **你的需求明确**:修改 CN-CLIP 配置,提供推理服务
191   -2. **不需要上游更新**:CLIP-as-service 已经很稳定
192   -3. **最简单直接**:不需要维护额外的仓库
193   -4. **易于管理**:所有代码在一个地方
194   -
195   -## 执行方案 A 的详细步骤
196   -
197   -```bash
198   -#!/bin/bash
199   -# 这个脚本会自动执行方案 A 的所有步骤
200   -
201   -set -e
202   -
203   -echo "=========================================="
204   -echo "将 clip-as-service 从子模块转为普通目录"
205   -echo "=========================================="
206   -
207   -cd /data/tw/SearchEngine
208   -
209   -# 1. 备份当前状态(可选)
210   -echo "步骤 1: 备份当前配置..."
211   -if [ -f ".gitmodules" ]; then
212   - cp .gitmodules .gitmodules.backup
213   - echo "✓ 已备份 .gitmodules"
214   -fi
215   -
216   -# 2. 移除 Git 对子模块的追踪
217   -echo "步骤 2: 移除子模块追踪..."
218   -git rm --cached third-party/clip-as-service || true
219   -
220   -# 3. 删除 .gitmodules 中的子模块配置
221   -echo "步骤 3: 清理 .gitmodules..."
222   -if [ -f ".gitmodules" ]; then
223   - # 使用 sed 删除 clip-as-service 相关的配置块
224   - sed -i '/clip-as-service/,+3 d' .gitmodules
225   -
226   - # 如果文件为空,删除它
227   - if [ ! -s ".gitmodules" ]; then
228   - rm .gitmodules
229   - echo "✓ 已删除空的 .gitmodules 文件"
230   - else
231   - echo "✓ 已更新 .gitmodules"
232   - fi
233   -fi
234   -
235   -# 4. 删除子模块的 Git 仓库
236   -echo "步骤 4: 删除子模块 Git 仓库..."
237   -rm -rf third-party/clip-as-service/.git
238   -echo "✓ 已删除子模块 Git 仓库"
239   -
240   -# 5. 将子模块目录作为普通目录添加
241   -echo "步骤 5: 将目录添加到 Git..."
242   -git add third-party/clip-as-service/
243   -git add .gitmodules 2>/dev/null || true
244   -echo "✓ 已添加到 Git"
245   -
246   -# 6. 显示状态
247   -echo ""
248   -echo "=========================================="
249   -echo "操作完成!当前状态:"
250   -echo "=========================================="
251   -git status
252   -
253   -echo ""
254   -echo "=========================================="
255   -echo "下一步:"
256   -echo "=========================================="
257   -echo "1. 检查修改: git diff --cached"
258   -echo "2. 提交更改: git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'"
259   -echo "3. 推送到远程: git push origin main"
260   -echo ""
261   -```
262   -
263   -## 验证转换成功
264   -
265   -转换后,你应该可以:
266   -
267   -```bash
268   -# 1. 正常查看文件状态
269   -git status
270   -
271   -# 2. 修改文件后直接添加
272   -# 编辑 third-party/clip-as-service/server/clip_server/torch-flow.yml
273   -git add third-party/clip-as-service/server/clip_server/torch-flow.yml
274   -git commit -m "config: 更新 CN-CLIP 配置"
275   -
276   -# 3. 推送到远程
277   -git push origin main
278   -
279   -# 4. 其他人克隆你的仓库后,不需要特殊的子模块命令
280   -# git clone https://your-repo-url
281   -# cd SearchEngine
282   -# 所有文件都已经存在,包括 third-party/clip-as-service
283   -```
284   -
285   -## 常见问题
286   -
287   -**Q: 转换后会增加仓库大小吗?**
288   -A: 会。clip-as-service 大约几十 MB。如果你担心仓库大小,可以使用 Git LFS 或 .gitignore 排除不必要的文件(如模型权重)。
289   -
290   -**Q: 如何后续获取原仓库的更新?**
291   -A: 使用方案 A 后,需要手动合并更新:
292   -```bash
293   -cd third-party/clip-as-service
294   -git remote add upstream https://github.com/jina-ai/clip-as-service.git
295   -git fetch upstream
296   -git merge upstream/main
297   -```
298   -
299   -**Q: 我可以回退到子模块吗?**
300   -A: 可以,但会比较复杂。建议在转换前提交一个保存点:
301   -```bash
302   -git branch backup-before-submodule-removal
303   -```
304   -
305   -**Q: 其他协作者需要注意什么?**
306   -A: 他们需要重新克隆仓库,或者:
307   -```bash
308   -# 删除旧的子模块引用
309   -git submodule deinit -f third-party/clip-as-service
310   -rm -rf .git/modules/third-party/clip-as-service
311   -
312   -# 重新拉取
313   -git pull origin main
314   -```
315   -
316   -## 总结
317   -
318   -- **推荐**: 方案 A(移除子模块)
319   -- **适用**: 你的使用场景
320   -- **优势**: 简单、直接、易于管理
321   -- **成本**: 略微增加仓库大小
322   -
323   -如果需要帮助执行这些步骤,请告诉我!
examples/clip_rest_api.py deleted
... ... @@ -1,254 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -CN-CLIP REST API 包装器
4   -
5   -提供 HTTP 接口,支持 curl 调用
6   -"""
7   -
8   -from flask import Flask, request, jsonify
9   -from flask_cors import CORS
10   -from clip_client import Client
11   -import numpy as np
12   -import traceback
13   -
14   -app = Flask(__name__)
15   -CORS(app) # 允许跨域请求
16   -
17   -# 连接到 CN-CLIP 服务
18   -try:
19   - client = Client('grpc://localhost:51000')
20   - print("✓ 已连接到 CN-CLIP 服务 (grpc://localhost:51000)")
21   -except Exception as e:
22   - print(f"✗ 连接失败: {e}")
23   - print("请先启动 CN-CLIP 服务: ./scripts/start_cnclip_service.sh")
24   - client = None
25   -
26   -
27   -@app.route('/health', methods=['GET'])
28   -def health():
29   - """健康检查"""
30   - return jsonify({
31   - 'status': 'ok' if client else 'error',
32   - 'service': 'cnclip-rest-api',
33   - 'backend': 'grpc://localhost:51000'
34   - })
35   -
36   -
37   -@app.route('/encode/text', methods=['POST'])
38   -def encode_text():
39   - """
40   - 编码文本
41   -
42   - 请求体:
43   - {
44   - "texts": ["文本1", "文本2"]
45   - }
46   -
47   - 返回:
48   - {
49   - "count": 2,
50   - "shape": [2, 1024],
51   - "embeddings": [[...], [...]]
52   - }
53   - """
54   - if not client:
55   - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503
56   -
57   - try:
58   - data = request.json
59   - texts = data.get('texts', [])
60   -
61   - if not texts:
62   - return jsonify({'error': '缺少 texts 参数'}), 400
63   -
64   - # 编码
65   - embeddings = client.encode(texts)
66   -
67   - return jsonify({
68   - 'count': len(texts),
69   - 'shape': embeddings.shape.tolist(),
70   - 'embeddings': embeddings.tolist()
71   - })
72   -
73   - except Exception as e:
74   - print(f"错误: {e}")
75   - print(traceback.format_exc())
76   - return jsonify({'error': str(e)}), 500
77   -
78   -
79   -@app.route('/encode/image', methods=['POST'])
80   -def encode_image():
81   - """
82   - 编码图像
83   -
84   - 请求体:
85   - {
86   - "images": ["https://example.com/image.jpg", "/path/to/local.jpg"]
87   - }
88   -
89   - 返回:
90   - {
91   - "count": 2,
92   - "shape": [2, 1024],
93   - "embeddings": [[...], [...]]
94   - }
95   - """
96   - if not client:
97   - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503
98   -
99   - try:
100   - data = request.json
101   - images = data.get('images', [])
102   -
103   - if not images:
104   - return jsonify({'error': '缺少 images 参数'}), 400
105   -
106   - # 编码
107   - embeddings = client.encode(images)
108   -
109   - return jsonify({
110   - 'count': len(images),
111   - 'shape': embeddings.shape.tolist(),
112   - 'embeddings': embeddings.tolist()
113   - })
114   -
115   - except Exception as e:
116   - print(f"错误: {e}")
117   - print(traceback.format_exc())
118   - return jsonify({'error': str(e)}), 500
119   -
120   -
121   -@app.route('/encode/mixed', methods=['POST'])
122   -def encode_mixed():
123   - """
124   - 混合编码(文本+图像)
125   -
126   - 请求体:
127   - {
128   - "data": ["文本", "https://example.com/image.jpg"]
129   - }
130   -
131   - 返回:
132   - {
133   - "count": 2,
134   - "shape": [2, 1024],
135   - "embeddings": [[...], [...]]
136   - }
137   - """
138   - if not client:
139   - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503
140   -
141   - try:
142   - data = request.json
143   - mixed_data = data.get('data', [])
144   -
145   - if not mixed_data:
146   - return jsonify({'error': '缺少 data 参数'}), 400
147   -
148   - # 编码
149   - embeddings = client.encode(mixed_data)
150   -
151   - return jsonify({
152   - 'count': len(mixed_data),
153   - 'shape': embeddings.shape.tolist(),
154   - 'embeddings': embeddings.tolist()
155   - })
156   -
157   - except Exception as e:
158   - print(f"错误: {e}")
159   - print(traceback.format_exc())
160   - return jsonify({'error': str(e)}), 500
161   -
162   -
163   -@app.route('/similarity', methods=['POST'])
164   -def similarity():
165   - """
166   - 计算相似度
167   -
168   - 请求体:
169   - {
170   - "text": "查询文本",
171   - "images": ["url1", "url2"],
172   - "texts": ["文本1", "文本2"]
173   - }
174   -
175   - 返回:
176   - {
177   - "image_similarities": [0.8, 0.3],
178   - "text_similarities": [0.9, 0.2]
179   - }
180   - """
181   - if not client:
182   - return jsonify({'error': 'CN-CLIP 服务未连接'}), 503
183   -
184   - try:
185   - data = request.json
186   - query_text = data.get('text', '')
187   - images = data.get('images', [])
188   - texts = data.get('texts', [])
189   -
190   - if not query_text:
191   - return jsonify({'error': '缺少 text 参数'}), 400
192   -
193   - from sklearn.metrics.pairwise import cosine_similarity
194   -
195   - # 编码查询文本
196   - query_embedding = client.encode([query_text])
197   -
198   - result = {}
199   -
200   - # 计算与图像的相似度
201   - if images:
202   - image_embeddings = client.encode(images)
203   - similarities = cosine_similarity(query_embedding, image_embeddings)[0]
204   - result['image_similarities'] = similarities.tolist()
205   - result['image_urls'] = images
206   -
207   - # 计算与文本的相似度
208   - if texts:
209   - text_embeddings = client.encode(texts)
210   - similarities = cosine_similarity(query_embedding, text_embeddings)[0]
211   - result['text_similarities'] = similarities.tolist()
212   - result['texts'] = texts
213   -
214   - return jsonify(result)
215   -
216   - except Exception as e:
217   - print(f"错误: {e}")
218   - print(traceback.format_exc())
219   - return jsonify({'error': str(e)}), 500
220   -
221   -
222   -@app.errorhandler(404)
223   -def not_found(error):
224   - return jsonify({'error': '接口不存在'}), 404
225   -
226   -
227   -@app.errorhandler(500)
228   -def internal_error(error):
229   - return jsonify({'error': '服务器内部错误'}), 500
230   -
231   -
232   -if __name__ == '__main__':
233   - print("\n" + "=" * 60)
234   - print("CN-CLIP REST API 服务")
235   - print("=" * 60)
236   - print("\n服务地址: http://localhost:6000")
237   - print("\n可用接口:")
238   - print(" POST /health - 健康检查")
239   - print(" POST /encode/text - 编码文本")
240   - print(" POST /encode/image - 编码图像")
241   - print(" POST /encode/mixed - 混合编码")
242   - print(" POST /similarity - 计算相似度")
243   - print("\n示例:")
244   - print(" curl http://localhost:6000/health")
245   - print(" curl -X POST http://localhost:6000/encode/text -H 'Content-Type: application/json' -d '{\"texts\": [\"测试文本\"]}'")
246   - print("\n" + "=" * 60)
247   - print()
248   -
249   - app.run(
250   - host='0.0.0.0',
251   - port=6000,
252   - debug=True,
253   - use_reloader=False # 避免重复启动
254   - )
examples/simple_examples.py deleted
... ... @@ -1,93 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -CN-CLIP 简单示例
4   -
5   -最常用的文本和图像编码示例
6   -"""
7   -
8   -from clip_client import Client
9   -
10   -# 初始化客户端
11   -client = Client('grpc://localhost:51000')
12   -
13   -# ============================================================================
14   -# 示例 1: 编码文本
15   -# ============================================================================
16   -print("示例 1: 文本编码")
17   -print("-" * 50)
18   -
19   -texts = ['一只可爱的猫咪', '美丽的高山风景']
20   -embeddings = client.encode(texts)
21   -
22   -print(f"输入: {texts}")
23   -print(f"输出形状: {embeddings.shape}") # (2, 1024)
24   -print(f"✓ 编码完成\n")
25   -
26   -# ============================================================================
27   -# 示例 2: 编码图像(URL)
28   -# ============================================================================
29   -print("示例 2: 图像编码(URL)")
30   -print("-" * 50)
31   -
32   -image_url = "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"
33   -embedding = client.encode([image_url])
34   -
35   -print(f"输入: {image_url}")
36   -print(f"输出形状: {embedding.shape}") # (1, 1024)
37   -print(f"✓ 编码完成\n")
38   -
39   -# ============================================================================
40   -# 示例 3: 编码图像(本地路径)
41   -# ============================================================================
42   -print("示例 3: 图像编码(本地文件)")
43   -print("-" * 50)
44   -
45   -local_image = "/path/to/local/image.jpg"
46   -# embedding = client.encode([local_image])
47   -print(f"输入: {local_image}")
48   -print(f"用法: client.encode(['{local_image}'])")
49   -print(f"✓ 编码完成\n")
50   -
51   -# ============================================================================
52   -# 示例 4: 混合编码(文本+图像)
53   -# ============================================================================
54   -print("示例 4: 混合编码")
55   -print("-" * 50)
56   -
57   -mixed_data = [
58   - '一只可爱的猫咪', # 文本
59   - 'https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg', # 图像URL
60   -]
61   -
62   -embeddings = client.encode(mixed_data)
63   -print(f"输入: {mixed_data}")
64   -print(f"输出形状: {embeddings.shape}") # (2, 1024)
65   -print(f"✓ 编码完成\n")
66   -
67   -# ============================================================================
68   -# 示例 5: 批量编码
69   -# ============================================================================
70   -print("示例 5: 批量编码(推荐)")
71   -print("-" * 50)
72   -
73   -# 一次编码多条数据(更高效)
74   -batch_data = [f"文本 {i}" for i in range(10)]
75   -embeddings = client.encode(batch_data)
76   -
77   -print(f"输入: {len(batch_data)} 条文本")
78   -print(f"输出形状: {embeddings.shape}") # (10, 1024)
79   -print(f"✓ 批量编码完成\n")
80   -
81   -# ============================================================================
82   -# 重要提示
83   -# ============================================================================
84   -print("重要提示:")
85   -print("-" * 50)
86   -print("1. 输入必须是列表: client.encode(['文本']) ✓")
87   -print(" 不是单个字符串: client.encode('文本') ✗")
88   -print()
89   -print("2. 返回值是 numpy 数组,形状为 (N, 1024)")
90   -print(" N = 输入数据的数量")
91   -print()
92   -print("3. 图像支持 URL 和本地文件路径")
93   -print()
examples/test_cnclip_example.py deleted
... ... @@ -1,177 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -CN-CLIP 快速测试脚本
4   -
5   -测试文本和图像编码功能
6   -"""
7   -
8   -from clip_client import Client
9   -from sklearn.metrics.pairwise import cosine_similarity
10   -import numpy as np
11   -
12   -# 测试图片
13   -TEST_IMAGE = "https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"
14   -
15   -# 测试文本
16   -TEST_TEXTS = [
17   - "一只可爱的猫咪",
18   - "美丽的高山风景",
19   - "汽车在公路上行驶",
20   - "现代建筑",
21   -]
22   -
23   -def test_connection():
24   - """测试服务连接"""
25   - print("=" * 60)
26   - print("测试 1: 连接服务")
27   - print("=" * 60)
28   -
29   - try:
30   - client = Client('grpc://localhost:51000')
31   - print("✓ 服务连接成功")
32   - return client
33   - except Exception as e:
34   - print(f"✗ 连接失败: {e}")
35   - print("\n请确保服务已启动:")
36   - print(" ./scripts/start_cnclip_service.sh")
37   - return None
38   -
39   -def test_text_encoding(client):
40   - """测试文本编码"""
41   - print("\n" + "=" * 60)
42   - print("测试 2: 文本编码")
43   - print("=" * 60)
44   -
45   - print(f"\n测试文本:")
46   - for i, text in enumerate(TEST_TEXTS, 1):
47   - print(f" {i}. {text}")
48   -
49   - try:
50   - embeddings = client.encode(TEST_TEXTS)
51   - print(f"\n✓ 文本编码成功")
52   - print(f" 编码数量: {len(embeddings)}")
53   - print(f" 向量形状: {embeddings.shape}")
54   - print(f" 数据类型: {embeddings.dtype}")
55   - print(f" 值域: [{embeddings.min():.4f}, {embeddings.max():.4f}]")
56   - return embeddings
57   - except Exception as e:
58   - print(f"✗ 文本编码失败: {e}")
59   - return None
60   -
61   -def test_image_encoding(client):
62   - """测试图像编码"""
63   - print("\n" + "=" * 60)
64   - print("测试 3: 图像编码")
65   - print("=" * 60)
66   -
67   - print(f"\n测试图片: {TEST_IMAGE}")
68   -
69   - try:
70   - embeddings = client.encode([TEST_IMAGE])
71   - print(f"\n✓ 图像编码成功")
72   - print(f" 向量形状: {embeddings.shape}")
73   - print(f" 数据类型: {embeddings.dtype}")
74   - print(f" 值域: [{embeddings.min():.4f}, {embeddings.max():.4f}]")
75   - return embeddings
76   - except Exception as e:
77   - print(f"✗ 图像编码失败: {e}")
78   - return None
79   -
80   -def test_image_text_retrieval(client, image_embedding, text_embeddings):
81   - """测试图文检索"""
82   - print("\n" + "=" * 60)
83   - print("测试 4: 图文检索(计算相似度)")
84   - print("=" * 60)
85   -
86   - print(f"\n使用图片搜索最匹配的文本...")
87   -
88   - try:
89   - # 计算相似度
90   - similarities = cosine_similarity(image_embedding, text_embeddings)[0]
91   -
92   - print(f"\n相似度排序:")
93   - # 按相似度排序
94   - sorted_indices = np.argsort(similarities)[::-1]
95   -
96   - for rank, idx in enumerate(sorted_indices, 1):
97   - text = TEST_TEXTS[idx]
98   - score = similarities[idx]
99   - bar = "█" * int(score * 50)
100   - print(f" {rank}. {score:.4f} {bar} {text}")
101   -
102   - print(f"\n最佳匹配: {TEST_TEXTS[sorted_indices[0]]}")
103   - print(f"相似度分数: {similarities[sorted_indices[0]]:.4f}")
104   -
105   - return similarities
106   - except Exception as e:
107   - print(f"✗ 相似度计算失败: {e}")
108   - return None
109   -
110   -def test_batch_encoding(client):
111   - """测试批量编码"""
112   - print("\n" + "=" * 60)
113   - print("测试 5: 批量编码性能")
114   - print("=" * 60)
115   -
116   - import time
117   -
118   - # 准备测试数据
119   - batch_texts = [f"测试文本 {i}" for i in range(50)]
120   -
121   - print(f"\n编码 {len(batch_texts)} 条文本...")
122   -
123   - try:
124   - start = time.time()
125   - embeddings = client.encode(batch_texts)
126   - elapsed = time.time() - start
127   -
128   - print(f"\n✓ 批量编码成功")
129   - print(f" 耗时: {elapsed:.2f}秒")
130   - print(f" 速度: {len(batch_texts)/elapsed:.2f} 条/秒")
131   - print(f" 平均延迟: {elapsed/len(batch_texts)*1000:.2f}ms/条")
132   -
133   - except Exception as e:
134   - print(f"✗ 批量编码失败: {e}")
135   -
136   -def main():
137   - print("\n" + "=" * 60)
138   - print("CN-CLIP 服务测试")
139   - print("=" * 60)
140   - print(f"\n测试图片: {TEST_IMAGE}")
141   - print(f"服务地址: grpc://localhost:51000")
142   -
143   - # 测试连接
144   - client = test_connection()
145   - if not client:
146   - return
147   -
148   - # 测试文本编码
149   - text_embeddings = test_text_encoding(client)
150   - if text_embeddings is None:
151   - return
152   -
153   - # 测试图像编码
154   - image_embeddings = test_image_encoding(client)
155   - if image_embeddings is None:
156   - return
157   -
158   - # 测试图文检索
159   - test_image_text_retrieval(client, image_embeddings, text_embeddings)
160   -
161   - # 测试批量编码性能
162   - test_batch_encoding(client)
163   -
164   - # 总结
165   - print("\n" + "=" * 60)
166   - print("测试总结")
167   - print("=" * 60)
168   - print("\n✓ 所有测试通过!")
169   - print("\n服务运行正常,可以开始使用。")
170   - print("\n下一步:")
171   - print(" 1. 查看使用文档: cat docs/CNCLIP_USAGE_GUIDE.md")
172   - print(" 2. 集成到你的项目")
173   - print(" 3. 调整服务配置(如需要)")
174   - print()
175   -
176   -if __name__ == '__main__':
177   - main()
examples/test_curl_examples.sh deleted
... ... @@ -1,159 +0,0 @@
1   -#!/bin/bash
2   -
3   -###############################################################################
4   -# CN-CLIP REST API 快速测试脚本
5   -#
6   -# 用途:
7   -# 测试 REST API 的各种功能
8   -#
9   -# 使用方法:
10   -# ./examples/test_curl_examples.sh
11   -#
12   -###############################################################################
13   -
14   -set -e
15   -
16   -# 颜色定义
17   -RED='\033[0;31m'
18   -GREEN='\033[0;32m'
19   -YELLOW='\033[1;33m'
20   -BLUE='\033[0;34m'
21   -NC='\033[0m'
22   -
23   -API_URL="http://localhost:51000"
24   -TEST_IMAGE="https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg"
25   -
26   -echo -e "${BLUE}========================================${NC}"
27   -echo -e "${BLUE}CN-CLIP REST API 测试${NC}"
28   -echo -e "${BLUE}========================================${NC}"
29   -echo ""
30   -
31   -# 测试 1: 健康检查
32   -echo -e "${BLUE}测试 1: 健康检查${NC}"
33   -echo "curl ${API_URL}/health"
34   -echo ""
35   -
36   -# 测试 2: 编码文本
37   -echo -e "${BLUE}测试 2: 编码文本${NC}"
38   -echo "curl -X POST ${API_URL}/encode/text \\"
39   -echo " -H 'Content-Type: application/json' \\"
40   -echo " -d '{\"texts\": [\"一只可爱的猫咪\"]}'"
41   -echo ""
42   -
43   -response=$(curl -s -X POST "${API_URL}/encode/text" \
44   - -H "Content-Type: application/json" \
45   - -d '{"texts": ["一只可爱的猫咪"]}')
46   -
47   -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response"
48   -
49   -if echo "$response" | grep -q '"shape": \[1, 1024\]'; then
50   - echo -e "${GREEN}✓ 文本编码成功${NC}"
51   -else
52   - echo -e "${RED}✗ 文本编码失败${NC}"
53   -fi
54   -
55   -echo ""
56   -echo "按 Enter 继续..."
57   -read
58   -
59   -# 测试 3: 编码图像
60   -echo -e "${BLUE}测试 3: 编码图像${NC}"
61   -echo "curl -X POST ${API_URL}/encode/image \\"
62   -echo " -H 'Content-Type: application/json' \\"
63   -echo " -d '{\"images\": [\"${TEST_IMAGE}\"]}'"
64   -echo ""
65   -
66   -response=$(curl -s -X POST "${API_URL}/encode/image" \
67   - -H "Content-Type: application/json" \
68   - -d "{\"images\": [\"${TEST_IMAGE}\"]}")
69   -
70   -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response"
71   -
72   -if echo "$response" | grep -q '"shape": \[1, 1024\]'; then
73   - echo -e "${GREEN}✓ 图像编码成功${NC}"
74   -else
75   - echo -e "${RED}✗ 图像编码失败${NC}"
76   -fi
77   -
78   -echo ""
79   -echo "按 Enter 继续..."
80   -read
81   -
82   -# 测试 4: 批量编码
83   -echo -e "${BLUE}测试 4: 批量编码${NC}"
84   -echo "curl -X POST ${API_URL}/encode/text \\"
85   -echo " -H 'Content-Type: application/json' \\"
86   -echo " -d '{\"texts\": [\"文本1\", \"文本2\", \"文本3\"]}'"
87   -echo ""
88   -
89   -response=$(curl -s -X POST "${API_URL}/encode/text" \
90   - -H "Content-Type: application/json" \
91   - -d '{"texts": ["文本1", "文本2", "文本3"]}')
92   -
93   -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response"
94   -
95   -if echo "$response" | grep -q '"shape": \[3, 1024\]'; then
96   - echo -e "${GREEN}✓ 批量编码成功${NC}"
97   -else
98   - echo -e "${RED}✗ 批量编码失败${NC}"
99   -fi
100   -
101   -echo ""
102   -echo "按 Enter 继续..."
103   -read
104   -
105   -# 测试 5: 相似度计算
106   -echo -e "${BLUE}测试 5: 相似度计算${NC}"
107   -echo "curl -X POST ${API_URL}/similarity \\"
108   -echo " -H 'Content-Type: application/json' \\"
109   -echo " -d '{\"text\": \"可爱的猫咪\", \"texts\": [\"一只可爱的小猫\", \"美丽的高山\"]}'"
110   -echo ""
111   -
112   -response=$(curl -s -X POST "${API_URL}/similarity" \
113   - -H "Content-Type: application/json" \
114   - -d '{"text": "可爱的猫咪", "texts": ["一只可爱的小猫", "美丽的高山"]}')
115   -
116   -echo "$response" | python3 -m json.tool 2>/dev/null || echo "$response"
117   -
118   -if echo "$response" | grep -q '"text_similarities"'; then
119   - echo -e "${GREEN}✓ 相似度计算成功${NC}"
120   -else
121   - echo -e "${RED}✗ 相似度计算失败${NC}"
122   -fi
123   -
124   -echo ""
125   -echo "按 Enter 继续..."
126   -read
127   -
128   -# 测试 6: 混合编码
129   -echo -e "${BLUE}测试 6: 混合编码(文本+图像)${NC}"
130   -echo "curl -X POST ${API_URL}/encode/mixed \\"
131   -echo " -H 'Content-Type: application/json' \\"
132   -echo " -d '{\"data\": [\"一只可爱的猫咪\", \"${TEST_IMAGE}\"]}'"
133   -echo ""
134   -
135   -response=$(curl -s -X POST "${API_URL}/encode/mixed" \
136   - -H "Content-Type: application/json" \
137   - -d "{\"data\": [\"一只可爱的猫咪\", \"${TEST_IMAGE}\"]}")
138   -
139   -echo "$response" | python3 -m json.tool 2>/dev/null | head -20 || echo "$response"
140   -
141   -if echo "$response" | grep -q '"shape": \[2, 1024\]'; then
142   - echo -e "${GREEN}✓ 混合编码成功${NC}"
143   -else
144   - echo -e "${RED}✗ 混合编码失败${NC}"
145   -fi
146   -
147   -# 总结
148   -echo ""
149   -echo -e "${BLUE}========================================${NC}"
150   -echo -e "${BLUE}测试总结${NC}"
151   -echo -e "${BLUE}========================================${NC}"
152   -echo ""
153   -echo -e "${GREEN}✓ 所有测试完成!${NC}"
154   -echo ""
155   -echo -e "下一步:"
156   -echo " 1. 查看使用文档: cat docs/CNCLIP_CURL_GUIDE.md"
157   -echo " 2. 在你的代码中调用 API"
158   -echo " 3. 集成到你的应用"
159   -echo ""
scripts/compare_index_mappings.py deleted
... ... @@ -1,189 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -对比不同租户索引的 mapping 结构
4   -"""
5   -
6   -import os
7   -import sys
8   -import json
9   -from pathlib import Path
10   -from typing import Dict, Any
11   -
12   -sys.path.insert(0, str(Path(__file__).parent.parent))
13   -
14   -from utils.es_client import get_es_client_from_env
15   -
16   -
17   -def get_field_type(mapping_dict: Dict, field_path: str) -> Dict[str, Any]:
18   - """递归获取字段的 mapping 信息"""
19   - parts = field_path.split('.')
20   - current = mapping_dict
21   -
22   - for part in parts:
23   - if isinstance(current, dict):
24   - current = current.get(part)
25   - if current is None:
26   - return None
27   - else:
28   - return None
29   - return current
30   -
31   -
32   -def compare_mappings(mapping1: Dict[str, Any], mapping2: Dict[str, Any], index1_name: str, index2_name: str):
33   - """对比两个索引的 mapping"""
34   - props1 = mapping1.get('mappings', {}).get('properties', {})
35   - props2 = mapping2.get('mappings', {}).get('properties', {})
36   -
37   - all_fields = set(props1.keys()) | set(props2.keys())
38   -
39   - print(f"\n{'='*80}")
40   - print(f"对比索引映射结构")
41   - print(f"{'='*80}")
42   - print(f"索引1: {index1_name}")
43   - print(f"索引2: {index2_name}")
44   - print(f"{'='*80}\n")
45   -
46   - differences = []
47   - same_fields = []
48   -
49   - for field in sorted(all_fields):
50   - field1 = props1.get(field)
51   - field2 = props2.get(field)
52   -
53   - if field1 is None:
54   - differences.append((field, f"只在 {index2_name} 中存在", field2))
55   - continue
56   - if field2 is None:
57   - differences.append((field, f"只在 {index1_name} 中存在", field1))
58   - continue
59   -
60   - type1 = field1.get('type')
61   - type2 = field2.get('type')
62   -
63   - if type1 != type2:
64   - differences.append((field, f"类型不同: {index1_name}={type1}, {index2_name}={type2}", (field1, field2)))
65   - else:
66   - same_fields.append((field, type1))
67   -
68   - # 打印相同的字段
69   - print(f"✓ 相同字段 ({len(same_fields)} 个):")
70   - for field, field_type in same_fields[:20]: # 只显示前20个
71   - print(f" - {field}: {field_type}")
72   - if len(same_fields) > 20:
73   - print(f" ... 还有 {len(same_fields) - 20} 个相同字段")
74   -
75   - # 打印不同的字段
76   - if differences:
77   - print(f"\n✗ 不同字段 ({len(differences)} 个):")
78   - for field, reason, details in differences:
79   - print(f"\n {field}:")
80   - print(f" {reason}")
81   - if isinstance(details, tuple):
82   - print(f" {index1_name}: {json.dumps(details[0], indent=4, ensure_ascii=False)}")
83   - print(f" {index2_name}: {json.dumps(details[1], indent=4, ensure_ascii=False)}")
84   - else:
85   - print(f" 详情: {json.dumps(details, indent=4, ensure_ascii=False)}")
86   - else:
87   - print(f"\n✓ 所有字段类型一致!")
88   -
89   - # 特别检查 tags 字段
90   - print(f"\n{'='*80}")
91   - print(f"特别检查: tags 字段")
92   - print(f"{'='*80}")
93   -
94   - tags1 = get_field_type(props1, 'tags')
95   - tags2 = get_field_type(props2, 'tags')
96   -
97   - if tags1:
98   - print(f"\n{index1_name}.tags:")
99   - print(f" 类型: {tags1.get('type')}")
100   - print(f" 完整定义: {json.dumps(tags1, indent=2, ensure_ascii=False)}")
101   - else:
102   - print(f"\n{index1_name}.tags: 不存在")
103   -
104   - if tags2:
105   - print(f"\n{index2_name}.tags:")
106   - print(f" 类型: {tags2.get('type')}")
107   - print(f" 完整定义: {json.dumps(tags2, indent=2, ensure_ascii=False)}")
108   - else:
109   - print(f"\n{index2_name}.tags: 不存在")
110   -
111   -
112   -def main():
113   - import argparse
114   -
115   - parser = argparse.ArgumentParser(description='对比 Elasticsearch 索引的 mapping 结构')
116   - parser.add_argument('index1', help='第一个索引名称 (例如: search_products_tenant_171)')
117   - parser.add_argument('index2', nargs='?', help='第二个索引名称 (例如: search_products_tenant_162)')
118   - parser.add_argument('--list', action='store_true', help='列出所有以 index1 为前缀的索引')
119   -
120   - args = parser.parse_args()
121   -
122   - # 连接 ES
123   - try:
124   - es_client = get_es_client_from_env()
125   - if not es_client.ping():
126   - print("✗ 无法连接到 Elasticsearch")
127   - return 1
128   - print("✓ Elasticsearch 连接成功\n")
129   - except Exception as e:
130   - print(f"✗ 连接 Elasticsearch 失败: {e}")
131   - return 1
132   -
133   - # 如果指定了 --list,列出所有匹配的索引
134   - if args.list or not args.index2:
135   - try:
136   - # 使用 cat API 列出所有索引
137   - indices = es_client.client.cat.indices(format='json')
138   - matching_indices = [idx['index'] for idx in indices if idx['index'].startswith(args.index1)]
139   -
140   - if matching_indices:
141   - print(f"找到 {len(matching_indices)} 个匹配的索引:")
142   - for idx in sorted(matching_indices):
143   - print(f" - {idx}")
144   - return 0
145   - else:
146   - print(f"未找到以 '{args.index1}' 开头的索引")
147   - return 1
148   - except Exception as e:
149   - print(f"✗ 列出索引失败: {e}")
150   - return 1
151   -
152   - # 获取两个索引的 mapping
153   - index1 = args.index1
154   - index2 = args.index2
155   -
156   - print(f"正在获取索引映射...")
157   - print(f" 索引1: {index1}")
158   - print(f" 索引2: {index2}\n")
159   -
160   - # 检查索引是否存在
161   - if not es_client.index_exists(index1):
162   - print(f"✗ 索引 '{index1}' 不存在")
163   - return 1
164   -
165   - if not es_client.index_exists(index2):
166   - print(f"✗ 索引 '{index2}' 不存在")
167   - return 1
168   -
169   - # 获取 mapping
170   - mapping1 = es_client.get_mapping(index1)
171   - mapping2 = es_client.get_mapping(index2)
172   -
173   - if not mapping1 or index1 not in mapping1:
174   - print(f"✗ 无法获取索引 '{index1}' 的映射")
175   - return 1
176   -
177   - if not mapping2 or index2 not in mapping2:
178   - print(f"✗ 无法获取索引 '{index2}' 的映射")
179   - return 1
180   -
181   - # 对比 mapping
182   - compare_mappings(mapping1[index1], mapping2[index2], index1, index2)
183   -
184   - return 0
185   -
186   -
187   -if __name__ == '__main__':
188   - sys.exit(main())
189   -
scripts/remove_clip_submodule.sh deleted
... ... @@ -1,210 +0,0 @@
1   -#!/bin/bash
2   -
3   -###############################################################################
4   -# 将 clip-as-service 从 Git 子模块转为普通目录
5   -#
6   -# 用途:
7   -# 移除 Git 子模块配置,将 clip-as-service 直接纳入主项目管理
8   -#
9   -# 使用方法:
10   -# ./scripts/remove_clip_submodule.sh
11   -#
12   -# 注意:
13   -# - 此操作会修改 Git 配置,请确保已提交当前更改
14   -# - 建议先创建备份分支
15   -# - 执行前请先阅读 docs/GIT_SUBMODULE_GUIDE.md
16   -#
17   -###############################################################################
18   -
19   -set -e # 遇到错误立即退出
20   -
21   -# 颜色定义
22   -RED='\033[0;31m'
23   -GREEN='\033[0;32m'
24   -YELLOW='\033[1;33m'
25   -BLUE='\033[0;34m'
26   -NC='\033[0m' # No Color
27   -
28   -# 项目路径
29   -PROJECT_ROOT="/data/tw/SearchEngine"
30   -
31   -echo -e "${BLUE}========================================${NC}"
32   -echo -e "${BLUE}将 clip-as-service 从子模块转为普通目录${NC}"
33   -echo -e "${BLUE}========================================${NC}"
34   -echo ""
35   -
36   -# 检查是否在项目根目录
37   -if [ ! -f "${PROJECT_ROOT}/.git" ] && [ ! -d "${PROJECT_ROOT}/.git" ]; then
38   - echo -e "${RED}错误: 当前目录不是 Git 仓库根目录${NC}"
39   - echo -e "${YELLOW}请在项目根目录运行此脚本${NC}"
40   - exit 1
41   -fi
42   -
43   -cd "${PROJECT_ROOT}"
44   -
45   -# 检查是否有未提交的更改
46   -if [ -n "$(git status --porcelain)" ]; then
47   - echo -e "${RED}错误: Git 工作区有未提交的更改${NC}"
48   - echo -e "${YELLOW}请先提交或暂存所有更改${NC}"
49   - echo ""
50   - echo "未提交的文件:"
51   - git status --short
52   - echo ""
53   - echo -e "${YELLOW}建议操作:${NC}"
54   - echo " 1. 提交更改: git add . && git commit -m '保存点'"
55   - echo " 2. 或创建备份分支: git branch backup-before-submodule-removal"
56   - exit 1
57   -fi
58   -
59   -# 确认操作
60   -echo -e "${YELLOW}此操作将会:${NC}"
61   -echo " 1. 移除 clip-as-service 的子模块配置"
62   -echo " 2. 将其作为普通目录纳入 Git 管理"
63   -echo " 3. 更新 .gitmodules 文件"
64   -echo ""
65   -echo -e "${YELLOW}请确保已阅读: docs/GIT_SUBMODULE_GUIDE.md${NC}"
66   -echo ""
67   -read -p "$(echo -e ${YELLOW}是否继续? [y/N]: ${NC})" -n 1 -r
68   -echo
69   -if [[ ! $REPLY =~ ^[Yy]$ ]]; then
70   - echo -e "${YELLOW}已取消操作${NC}"
71   - exit 0
72   -fi
73   -
74   -# 创建备份分支
75   -BACKUP_BRANCH="backup-before-submodule-removal-$(date +%Y%m%d-%H%M%S)"
76   -echo -e "${BLUE}创建备份分支: ${BACKUP_BRANCH}${NC}"
77   -git branch "${BACKUP_BRANCH}"
78   -echo -e "${GREEN}✓ 备份分支已创建${NC}"
79   -echo ""
80   -
81   -# 步骤 1: 备份 .gitmodules
82   -echo -e "${BLUE}步骤 1: 备份配置文件...${NC}"
83   -if [ -f ".gitmodules" ]; then
84   - cp .gitmodules .gitmodules.backup
85   - echo -e "${GREEN}✓ 已备份 .gitmodules → .gitmodules.backup${NC}"
86   -fi
87   -
88   -# 步骤 2: 移除子模块追踪
89   -echo -e "${BLUE}步骤 2: 移除子模块追踪...${NC}"
90   -if git ls-files --stage | grep -q "160000.*third-party/clip-as-service"; then
91   - git rm --cached third-party/clip-as-service
92   - echo -e "${GREEN}✓ 已移除子模块缓存${NC}"
93   -else
94   - echo -e "${YELLOW}⚠ 子模块未在 Git 索引中,跳过此步骤${NC}"
95   -fi
96   -
97   -# 步骤 3: 清理 .gitmodules
98   -echo -e "${BLUE}步骤 3: 清理 .gitmodules...${NC}"
99   -if [ -f ".gitmodules" ]; then
100   - # 删除 clip-as-service 相关的配置块
101   - # 从 [submodule "third-party/clip-as-service"] 到下一个空行或文件末尾
102   - sed -i '/\[submodule "third-party\/clip-as-service"\]/,/\s*$/d' .gitmodules
103   -
104   - # 如果文件为空或只包含空行,删除它
105   - if [ ! -s ".gitmodules" ] || [ $(wc -l < .gitmodules) -eq 0 ]; then
106   - rm .gitmodules
107   - echo -e "${GREEN}✓ 已删除空的 .gitmodules 文件${NC}"
108   - else
109   - echo -e "${GREEN}✓ 已更新 .gitmodules${NC}"
110   - fi
111   -else
112   - echo -e "${YELLOW}⚠ .gitmodules 文件不存在,跳过此步骤${NC}"
113   -fi
114   -
115   -# 步骤 4: 删除子模块的 Git 仓库
116   -echo -e "${BLUE}步骤 4: 删除子模块 Git 仓库...${NC}"
117   -if [ -d "third-party/clip-as-service/.git" ]; then
118   - rm -rf third-party/clip-as-service/.git
119   - echo -e "${GREEN}✓ 已删除子模块 Git 仓库${NC}"
120   -else
121   - echo -e "${YELLOW}⚠ 子模块 Git 仓库不存在,跳过此步骤${NC}"
122   -fi
123   -
124   -# 步骤 5: 清理 Git 模块缓存
125   -echo -e "${BLUE}步骤 5: 清理 Git 模块缓存...${NC}"
126   -if [ -d ".git/modules/third-party/clip-as-service" ]; then
127   - rm -rf .git/modules/third-party/clip-as-service
128   - echo -e "${GREEN}✓ 已清理 Git 模块缓存${NC}"
129   -else
130   - echo -e "${YELLOW}⚠ Git 模块缓存不存在,跳过此步骤${NC}"
131   -fi
132   -
133   -# 步骤 6: 将目录作为普通文件添加
134   -echo -e "${BLUE}步骤 6: 将目录添加到 Git...${NC}"
135   -git add third-party/clip-as-service/
136   -if [ -f ".gitmodules" ]; then
137   - git add .gitmodules
138   -fi
139   -echo -e "${GREEN}✓ 已添加到 Git${NC}"
140   -
141   -# 显示状态
142   -echo ""
143   -echo -e "${BLUE}========================================${NC}"
144   -echo -e "${BLUE}操作完成!当前状态:${NC}"
145   -echo -e "${BLUE}========================================${NC}"
146   -echo ""
147   -git status
148   -echo ""
149   -
150   -# 统计信息
151   -echo -e "${BLUE}统计信息:${NC}"
152   -FILE_COUNT=$(find third-party/clip-as-service -type f | wc -l)
153   -DIR_SIZE=$(du -sh third-party/clip-as-service | cut -f1)
154   -echo " 文件数量: ${FILE_COUNT}"
155   -echo " 目录大小: ${DIR_SIZE}"
156   -echo ""
157   -
158   -# 下一步提示
159   -echo -e "${GREEN}========================================${NC}"
160   -echo -e "${GREEN}✓ 子模块转换完成!${NC}"
161   -echo -e "${GREEN}========================================${NC}"
162   -echo ""
163   -echo -e "${BLUE}下一步操作:${NC}"
164   -echo ""
165   -echo "1. 查看将要提交的更改:"
166   -echo " git diff --cached --stat"
167   -echo ""
168   -echo "2. 提交更改:"
169   -echo " git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'"
170   -echo ""
171   -echo "3. 推送到远程:"
172   -echo " git push origin main"
173   -echo ""
174   -echo -e "${BLUE}注意事项:${NC}"
175   -echo " - 备份分支: ${BACKUP_BRANCH}"
176   -echo " - 如需回退: git reset --hard ${BACKUP_BRANCH}"
177   -echo " - 配置备份: .gitmodules.backup"
178   -echo ""
179   -echo -e "${BLUE}验证转换:${NC}"
180   -echo " cd third-party/clip-as-service"
181   -echo " git status # 应该显示 'not a git repository'"
182   -echo ""
183   -
184   -# 提示查看详细文档
185   -echo -e "${YELLOW}📖 详细文档: docs/GIT_SUBMODULE_GUIDE.md${NC}"
186   -echo ""
187   -
188   -# 询问是否立即提交
189   -read -p "$(echo -e ${YELLOW}是否立即提交更改? [Y/n]: ${NC})" -n 1 -r
190   -echo
191   -if [[ $REPLY =~ ^[Yy]$ ]] || [ -z "$REPLY" ]; then
192   - echo -e "${BLUE}正在提交更改...${NC}"
193   - git commit -m "feat: 将 clip-as-service 从子模块转为普通目录
194   -
195   -- 移除子模块配置,改为普通目录
196   -- 便于自定义配置和管理
197   -- 备份分支: ${BACKUP_BRANCH}
198   -"
199   - echo -e "${GREEN}✓ 已提交${NC}"
200   - echo ""
201   - echo -e "${YELLOW}现在可以推送到远程仓库:${NC}"
202   - echo " git push origin main"
203   -else
204   - echo -e "${YELLOW}跳过提交${NC}"
205   - echo -e "${YELLOW}你可以稍后手动提交:${NC}"
206   - echo " git commit -m 'feat: 将 clip-as-service 从子模块转为普通目录'"
207   -fi
208   -
209   -echo ""
210   -echo -e "${GREEN}所有操作完成!${NC}"
scripts/start_clip_api.sh deleted
... ... @@ -1,154 +0,0 @@
1   -#!/bin/bash
2   -
3   -###############################################################################
4   -# CN-CLIP REST API 启动脚本
5   -#
6   -# 用途:
7   -# 启动 REST API 服务,提供 HTTP 接口供 curl 调用
8   -#
9   -# 使用方法:
10   -# ./scripts/start_clip_api.sh
11   -#
12   -###############################################################################
13   -
14   -set -e
15   -
16   -# 颜色定义
17   -RED='\033[0;31m'
18   -GREEN='\033[0;32m'
19   -YELLOW='\033[1;33m'
20   -BLUE='\033[0;34m'
21   -NC='\033[0m'
22   -
23   -# 项目路径
24   -PROJECT_ROOT="/data/tw/SearchEngine"
25   -API_SCRIPT="${PROJECT_ROOT}/examples/clip_rest_api.py"
26   -PID_FILE="${PROJECT_ROOT}/logs/clip_rest_api.pid"
27   -LOG_FILE="${PROJECT_ROOT}/logs/clip_rest_api.log"
28   -
29   -echo -e "${BLUE}========================================${NC}"
30   -echo -e "${BLUE}启动 CN-CLIP REST API 服务${NC}"
31   -echo -e "${BLUE}========================================${NC}"
32   -echo ""
33   -
34   -# 检查 API 脚本
35   -if [ ! -f "${API_SCRIPT}" ]; then
36   - echo -e "${RED}错误: API 脚本不存在: ${API_SCRIPT}${NC}"
37   - exit 1
38   -fi
39   -
40   -# 创建日志目录
41   -mkdir -p "$(dirname "${LOG_FILE}")"
42   -
43   -# 检查是否已有服务运行
44   -if [ -f "${PID_FILE}" ]; then
45   - OLD_PID=$(cat "${PID_FILE}")
46   - if ps -p ${OLD_PID} > /dev/null 2>&1; then
47   - echo -e "${YELLOW}警告: REST API 服务已在运行 (PID: ${OLD_PID})${NC}"
48   - echo -e "${YELLOW}如需重启,请先运行: ./scripts/stop_clip_api.sh${NC}"
49   - exit 0
50   - else
51   - echo -e "${YELLOW}清理旧的 PID 文件${NC}"
52   - rm -f "${PID_FILE}"
53   - fi
54   -fi
55   -
56   -# 检查端口是否被占用
57   -if lsof -Pi :6000 -sTCP:LISTEN -t >/dev/null 2>&1; then
58   - echo -e "${RED}错误: 端口 6000 已被占用${NC}"
59   - echo -e "${YELLOW}请检查是否有其他服务正在使用该端口${NC}"
60   - lsof -i :6000 | grep LISTEN || true
61   - exit 1
62   -fi
63   -
64   -# 检查 conda 环境
65   -if [ -z "${CONDA_DEFAULT_ENV}" ] || [ "${CONDA_DEFAULT_ENV}" != "clip_service" ]; then
66   - echo -e "${YELLOW}激活 clip_service 环境...${NC}"
67   - if [ -f "/home/tw/miniconda3/etc/profile.d/conda.sh" ]; then
68   - source "/home/tw/miniconda3/etc/profile.d/conda.sh"
69   - conda activate clip_service
70   - else
71   - echo -e "${RED}错误: 无法找到 conda${NC}"
72   - exit 1
73   - fi
74   -fi
75   -
76   -# 检查依赖
77   -echo -e "${BLUE}检查依赖...${NC}"
78   -python -c "import flask" 2>/dev/null || {
79   - echo -e "${YELLOW}Flask 未安装,正在安装...${NC}"
80   - pip install flask flask-cors scikit-learn
81   -}
82   -
83   -# 检查 CN-CLIP 服务
84   -echo -e "${BLUE}检查 CN-CLIP 服务...${NC}"
85   -if ! ps aux | grep "clip_server" | grep -v grep > /dev/null; then
86   - echo -e "${YELLOW}警告: CN-CLIP 服务未运行${NC}"
87   - echo -e "${YELLOW}请先启动: ./scripts/start_cnclip_service.sh${NC}"
88   - read -p "$(echo -e ${YELLOW}是否继续启动 REST API? [y/N]: ${NC})" -n 1 -r
89   - echo
90   - if [[ ! $REPLY =~ ^[Yy]$ ]]; then
91   - exit 0
92   - fi
93   -fi
94   -
95   -# 启动 REST API 服务
96   -echo -e "${BLUE}正在启动 REST API 服务...${NC}"
97   -cd "${PROJECT_ROOT}"
98   -
99   -nohup python "${API_SCRIPT}" > "${LOG_FILE}" 2>&1 &
100   -API_PID=$!
101   -echo ${API_PID} > "${PID_FILE}"
102   -
103   -# 等待服务启动
104   -echo -e "${YELLOW}等待服务启动...${NC}"
105   -sleep 3
106   -
107   -# 检查服务是否启动成功
108   -if ps -p ${API_PID} > /dev/null 2>&1; then
109   - # 检查端口是否监听
110   - if lsof -Pi :6000 -sTCP:LISTEN -t >/dev/null 2>&1; then
111   - echo -e "${GREEN}========================================${NC}"
112   - echo -e "${GREEN}✓ REST API 服务启动成功!${NC}"
113   - echo -e "${GREEN}========================================${NC}"
114   - echo ""
115   - echo -e "服务信息:"
116   - echo -e " PID: ${API_PID}"
117   - echo -e " 端口: 6000"
118   - echo -e " 日志文件: ${LOG_FILE}"
119   - echo ""
120   - echo -e "测试服务:"
121   - echo -e " curl http://localhost:6000/health"
122   - echo ""
123   - echo -e "使用示例:"
124   - echo -e " # 编码文本"
125   - echo -e " curl -X POST http://localhost:6000/encode/text \\"
126   - echo -e " -H 'Content-Type: application/json' \\"
127   - echo -e " -d '{\"texts\": [\"测试文本\"]}'"
128   - echo ""
129   - echo -e " # 编码图像"
130   - echo -e " curl -X POST http://localhost:6000/encode/image \\"
131   - echo -e " -H 'Content-Type: application/json' \\"
132   - echo -e " -d '{\"images\": [\"https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg\"]}'"
133   - echo ""
134   - echo -e "查看日志:"
135   - echo -e " tail -f ${LOG_FILE}"
136   - echo ""
137   - echo -e "停止服务:"
138   - echo -e " ./scripts/stop_clip_api.sh"
139   - echo ""
140   - else
141   - echo -e "${YELLOW}服务已启动,但端口尚未监听,请稍候...${NC}"
142   - echo -e "${YELLOW}查看日志: tail -f ${LOG_FILE}${NC}"
143   - fi
144   -else
145   - echo -e "${RED}========================================${NC}"
146   - echo -e "${RED}✗ 服务启动失败!${NC}"
147   - echo -e "${RED}========================================${NC}"
148   - echo ""
149   - echo -e "请查看日志获取详细错误信息:"
150   - echo -e " tail -f ${LOG_FILE}"
151   - echo ""
152   - rm -f "${PID_FILE}"
153   - exit 1
154   -fi
scripts/start_cnclip_service.sh
... ... @@ -42,6 +42,7 @@ DEFAULT_BATCH_SIZE=32
42 42 DEFAULT_NUM_WORKERS=4
43 43 DEFAULT_DTYPE="float16"
44 44 DEFAULT_MODEL_NAME="CN-CLIP/ViT-H-14"
  45 +# DEFAULT_MODEL_NAME="CN-CLIP/ViT-L-14-336"
45 46 DEFAULT_REPLICAS=1 # 副本数
46 47  
47 48 # 项目路径
... ...
scripts/stop_clip_api.sh deleted
... ... @@ -1,87 +0,0 @@
1   -#!/bin/bash
2   -
3   -###############################################################################
4   -# CN-CLIP REST API 停止脚本
5   -#
6   -# 用途:
7   -# 停止 REST API 服务
8   -#
9   -# 使用方法:
10   -# ./scripts/stop_clip_api.sh
11   -#
12   -###############################################################################
13   -
14   -set -e
15   -
16   -# 颜色定义
17   -RED='\033[0;31m'
18   -GREEN='\033[0;32m'
19   -YELLOW='\033[1;33m'
20   -BLUE='\033[0;34m'
21   -NC='\033[0m'
22   -
23   -# 项目路径
24   -PROJECT_ROOT="/data/tw/SearchEngine"
25   -PID_FILE="${PROJECT_ROOT}/logs/clip_rest_api.pid"
26   -
27   -echo -e "${BLUE}========================================${NC}"
28   -echo -e "${BLUE}停止 CN-CLIP REST API 服务${NC}"
29   -echo -e "${BLUE}========================================${NC}"
30   -echo ""
31   -
32   -# 检查 PID 文件
33   -if [ ! -f "${PID_FILE}" ]; then
34   - echo -e "${YELLOW}警告: 未找到 PID 文件${NC}"
35   - echo -e "${YELLOW}REST API 服务可能未运行${NC}"
36   -
37   - # 尝试查找并终止进程
38   - if ps aux | grep "clip_rest_api.py" | grep -v grep > /dev/null; then
39   - echo -e "${YELLOW}发现运行中的 API 进程${NC}"
40   - API_PIDS=$(ps aux | grep "clip_rest_api.py" | grep -v grep | awk '{print $2}')
41   - echo -e "${YELLOW}正在终止...${NC}"
42   - for PID in ${API_PIDS}; do
43   - kill ${PID} 2>/dev/null || true
44   - done
45   - sleep 1
46   - echo -e "${GREEN}✓ 进程已终止${NC}"
47   - fi
48   - exit 0
49   -fi
50   -
51   -# 读取 PID
52   -PID="$(cat "${PID_FILE}")"
53   -
54   -# 检查进程
55   -if ps -p "${PID}" > /dev/null 2>&1; then
56   - echo -e "${BLUE}服务信息:${NC}"
57   - echo " PID: ${PID}"
58   - echo ""
59   -
60   - echo -e "${YELLOW}正在停止服务...${NC}"
61   - kill "${PID}" || true
62   - sleep 2
63   -
64   - # 检查是否还在运行
65   - if ps -p "${PID}" > /dev/null 2>&1; then
66   - echo -e "${YELLOW}进程仍在运行,强制终止...${NC}"
67   - kill -9 "${PID}" || true
68   - sleep 1
69   - fi
70   -
71   - # 最终检查
72   - if ps -p "${PID}" > /dev/null 2>&1; then
73   - echo -e "${RED}错误: 无法停止进程${NC}"
74   - exit 1
75   - else
76   - echo -e "${GREEN}========================================${NC}"
77   - echo -e "${GREEN}✓ REST API 服务已停止${NC}"
78   - echo -e "${GREEN}========================================${NC}"
79   - fi
80   -else
81   - echo -e "${YELLOW}警告: 进程 ${PID} 不存在${NC}"
82   -fi
83   -
84   -# 清理 PID 文件
85   -rm -f "${PID_FILE}"
86   -echo ""
87   -echo -e "${GREEN}PID 文件已删除${NC}"
scripts/stop_cnclip_service.sh
... ... @@ -89,21 +89,42 @@ echo &quot;&quot;
89 89  
90 90 echo -e "${YELLOW}正在停止服务...${NC}"
91 91  
92   -# 发送 SIGTERM 信号
93   -kill "${PID}" || true
94   -sleep 2
95   -
96   -# 检查进程是否还在运行
97   -if ps -p "${PID}" > /dev/null 2>&1; then
98   - echo -e "${YELLOW}进程仍在运行,发送 SIGKILL...${NC}"
99   - kill -9 "${PID}" || true
  92 +# 查找所有相关的 clip_server 进程(通过配置文件路径)
  93 +CONFIG_FILE="torch-flow-temp.yml"
  94 +ALL_PIDS=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | awk '{print $2}')
  95 +
  96 +if [ -z "${ALL_PIDS}" ]; then
  97 + # 如果没有找到,至少尝试停止 PID 文件中的进程
  98 + ALL_PIDS="${PID}"
  99 +fi
  100 +
  101 +# 终止所有相关进程
  102 +for P in ${ALL_PIDS}; do
  103 + if ps -p "${P}" > /dev/null 2>&1; then
  104 + echo -e "${YELLOW}终止进程 ${P}...${NC}"
  105 + kill "${P}" 2>/dev/null || true
  106 + fi
  107 +done
  108 +
  109 +# 等待进程退出
  110 +sleep 3
  111 +
  112 +# 检查是否还有进程在运行,如果有则强制终止
  113 +REMAINING_PIDS=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | awk '{print $2}')
  114 +if [ -n "${REMAINING_PIDS}" ]; then
  115 + echo -e "${YELLOW}发现仍在运行的进程,强制终止...${NC}"
  116 + for P in ${REMAINING_PIDS}; do
  117 + echo -e "${YELLOW}强制终止进程 ${P}...${NC}"
  118 + kill -9 "${P}" 2>/dev/null || true
  119 + done
100 120 sleep 1
101 121 fi
102 122  
103 123 # 最终检查
104   -if ps -p "${PID}" > /dev/null 2>&1; then
105   - echo -e "${RED}错误: 无法停止进程 ${PID}${NC}"
106   - echo -e "${YELLOW}请手动停止: kill -9 ${PID}${NC}"
  124 +FINAL_CHECK=$(ps aux | grep "clip_server.*${CONFIG_FILE}" | grep -v grep | wc -l)
  125 +if [ "${FINAL_CHECK}" -gt 0 ]; then
  126 + echo -e "${RED}错误: 仍有进程无法停止${NC}"
  127 + echo -e "${YELLOW}请手动检查: ps aux | grep clip_server${NC}"
107 128 exit 1
108 129 else
109 130 echo -e "${GREEN}========================================${NC}"
... ...
scripts/test_cnclip_client.py deleted
... ... @@ -1,109 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -CN-CLIP 服务客户端测试脚本
4   -
5   -用法:
6   - python scripts/test_cnclip_client.py [--url URL]
7   -
8   -注意:如果服务配置了 protocol: http,必须使用 http:// 而不是 grpc://
9   -"""
10   -
11   -import sys
12   -import argparse
13   -from pathlib import Path
14   -
15   -# 添加项目路径
16   -project_root = Path(__file__).parent.parent
17   -sys.path.insert(0, str(project_root))
18   -
19   -try:
20   - from clip_client import Client
21   -except ImportError:
22   - print("错误: 请先安装 clip-client: pip install clip-client")
23   - sys.exit(1)
24   -
25   -
26   -def test_text_encoding(client):
27   - """测试文本编码"""
28   - print("\n测试文本编码...")
29   - try:
30   - texts = ['这是测试文本', '另一个测试文本']
31   - result = client.encode(texts)
32   - print(f"✓ 成功! 形状: {result.shape}")
33   - print(f" 输入: {len(texts)} 个文本")
34   - print(f" 输出维度: {result.shape[1]}")
35   - return True
36   - except Exception as e:
37   - print(f"✗ 失败: {e}")
38   - return False
39   -
40   -
41   -def test_image_encoding(client):
42   - """测试图像编码"""
43   - print("\n测试图像编码...")
44   - try:
45   - images = ['https://oss.essa.cn/98532128-cf8e-456c-9e30-6f2a5ea0c19f.jpg']
46   - result = client.encode(images)
47   - print(f"✓ 成功! 形状: {result.shape}")
48   - print(f" 输入: {len(images)} 个图像")
49   - print(f" 输出维度: {result.shape[1]}")
50   - return True
51   - except Exception as e:
52   - print(f"✗ 失败: {e}")
53   - print(" 注意: CN-CLIP 的图像编码可能存在兼容性问题")
54   - return False
55   -
56   -
57   -def main():
58   - parser = argparse.ArgumentParser(description='CN-CLIP 服务客户端测试')
59   - parser.add_argument(
60   - '--url',
61   - default='http://localhost:51000',
62   - help='服务地址(默认: http://localhost:51000)'
63   - )
64   -
65   - args = parser.parse_args()
66   -
67   - print("=" * 50)
68   - print("CN-CLIP 服务客户端测试")
69   - print("=" * 50)
70   - print(f"服务地址: {args.url}")
71   -
72   - # 检查协议
73   - if args.url.startswith('grpc://'):
74   - print("\n⚠ 警告: 服务配置了 protocol: http,请使用 http:// 而不是 grpc://")
75   - print(" 将自动转换为 http://")
76   - args.url = args.url.replace('grpc://', 'http://')
77   - print(f" 新地址: {args.url}")
78   -
79   - try:
80   - client = Client(args.url)
81   - print("✓ 客户端创建成功")
82   - except Exception as e:
83   - print(f"✗ 客户端创建失败: {e}")
84   - sys.exit(1)
85   -
86   - # 运行测试
87   - results = []
88   - results.append(test_text_encoding(client))
89   - results.append(test_image_encoding(client))
90   -
91   - # 汇总
92   - print("\n" + "=" * 50)
93   - print("测试结果汇总")
94   - print("=" * 50)
95   - print(f"总测试数: {len(results)}")
96   - print(f"通过: {sum(results)}")
97   - print(f"失败: {len(results) - sum(results)}")
98   -
99   - if all(results):
100   - print("\n✓ 所有测试通过!")
101   - sys.exit(0)
102   - else:
103   - print("\n✗ 部分测试失败")
104   - sys.exit(1)
105   -
106   -
107   -if __name__ == '__main__':
108   - main()
109   -
scripts/test_cnclip_service.py deleted
... ... @@ -1,320 +0,0 @@
1   -#!/usr/bin/env python3
2   -"""
3   -CN-CLIP 服务测试脚本
4   -
5   -用法:
6   - python scripts/test_cnclip_service.py
7   -
8   -选项:
9   - --url TEXT 服务地址(默认:grpc://localhost:51000)
10   - --text 只测试文本编码
11   - --image 只测试图像编码
12   - --batch-size INT 批处理大小(默认:10)
13   - --help 显示帮助信息
14   -"""
15   -
16   -import sys
17   -import time
18   -import argparse
19   -from pathlib import Path
20   -
21   -# 添加项目路径到 sys.path
22   -project_root = Path(__file__).parent.parent
23   -sys.path.insert(0, str(project_root))
24   -
25   -# 颜色输出
26   -class Colors:
27   - GREEN = '\033[0;32m'
28   - RED = '\033[0;31m'
29   - YELLOW = '\033[1;33m'
30   - BLUE = '\033[0;34m'
31   - NC = '\033[0m'
32   -
33   -
34   -def print_success(msg):
35   - print(f"{Colors.GREEN}✓ {msg}{Colors.NC}")
36   -
37   -
38   -def print_error(msg):
39   - print(f"{Colors.RED}✗ {msg}{Colors.NC}")
40   -
41   -
42   -def print_warning(msg):
43   - print(f"{Colors.YELLOW}⚠ {msg}{Colors.NC}")
44   -
45   -
46   -def print_info(msg):
47   - print(f"{Colors.BLUE}ℹ {msg}{Colors.NC}")
48   -
49   -
50   -def test_imports():
51   - """测试必要的依赖是否安装"""
52   - print("\n" + "="*50)
53   - print("测试 1: 检查依赖")
54   - print("="*50)
55   -
56   - try:
57   - import clip_client
58   - print_success("clip_client 已安装")
59   - except ImportError as e:
60   - print_error(f"clip_client 未安装: {e}")
61   - print_info("请运行: pip install clip-client")
62   - return False
63   -
64   - try:
65   - import numpy as np
66   - print_success("numpy 已安装")
67   - except ImportError as e:
68   - print_error(f"numpy 未安装: {e}")
69   - return False
70   -
71   - return True
72   -
73   -
74   -def test_connection(url):
75   - """测试服务连接"""
76   - print("\n" + "="*50)
77   - print("测试 2: 连接服务")
78   - print("="*50)
79   - print(f"服务地址: {url}")
80   -
81   - try:
82   - from clip_client import Client
83   -
84   - client = Client(url)
85   - print_success("客户端创建成功")
86   - return client
87   - except Exception as e:
88   - print_error(f"连接失败: {e}")
89   - print_info("请确保服务已启动: ./scripts/start_cnclip_service.sh")
90   - return None
91   -
92   -
93   -def test_text_encoding(client, batch_size=10):
94   - """测试文本编码"""
95   - print("\n" + "="*50)
96   - print("测试 3: 文本编码")
97   - print("="*50)
98   -
99   - try:
100   - # 准备测试数据
101   - test_texts = [
102   - '你好,世界',
103   - 'CN-CLIP 图像编码服务',
104   - '这是一个测试',
105   - '人工智能',
106   - '机器学习',
107   - '深度学习',
108   - '计算机视觉',
109   - '自然语言处理',
110   - '搜索引擎',
111   - '多模态检索',
112   - ][:batch_size]
113   -
114   - print(f"测试文本数量: {len(test_texts)}")
115   - print(f"示例文本: {test_texts[0]}")
116   -
117   - # 执行编码
118   - start_time = time.time()
119   - embeddings = client.encode(test_texts)
120   - elapsed_time = time.time() - start_time
121   -
122   - # 验证结果
123   - assert embeddings.shape[0] == len(test_texts), "向量数量不匹配"
124   - assert embeddings.shape[1] == 1024, "向量维度应该是 1024"
125   -
126   - print_success(f"编码成功")
127   - print(f" 向量形状: {embeddings.shape}")
128   - print(f" 耗时: {elapsed_time:.2f}秒")
129   - print(f" 速度: {len(test_texts)/elapsed_time:.2f} 条/秒")
130   - print(f" 数据类型: {embeddings.dtype}")
131   -
132   - return True
133   -
134   - except Exception as e:
135   - print_error(f"文本编码失败: {e}")
136   - return False
137   -
138   -
139   -def test_image_encoding(client, batch_size=5):
140   - """测试图像编码"""
141   - print("\n" + "="*50)
142   - print("测试 4: 图像编码")
143   - print("="*50)
144   -
145   - try:
146   - # 准备测试数据(使用在线图片)
147   - test_images = [
148   - 'https://picsum.photos/224',
149   - 'https://picsum.photos/224?random=1',
150   - 'https://picsum.photos/224?random=2',
151   - 'https://picsum.photos/224?random=3',
152   - 'https://picsum.photos/224?random=4',
153   - ][:batch_size]
154   -
155   - print(f"测试图像数量: {len(test_images)}")
156   - print(f"示例 URL: {test_images[0]}")
157   -
158   - # 执行编码
159   - start_time = time.time()
160   - embeddings = client.encode(test_images)
161   - elapsed_time = time.time() - start_time
162   -
163   - # 验证结果
164   - assert embeddings.shape[0] == len(test_images), "向量数量不匹配"
165   - assert embeddings.shape[1] == 1024, "向量维度应该是 1024"
166   -
167   - print_success(f"编码成功")
168   - print(f" 向量形状: {embeddings.shape}")
169   - print(f" 耗时: {elapsed_time:.2f}秒")
170   - print(f" 速度: {len(test_images)/elapsed_time:.2f} 条/秒")
171   - print(f" 数据类型: {embeddings.dtype}")
172   -
173   - return True
174   -
175   - except Exception as e:
176   - print_error(f"图像编码失败: {e}")
177   - print_warning("可能需要网络连接来下载测试图片")
178   - return False
179   -
180   -
181   -def test_mixed_encoding(client):
182   - """测试混合编码(文本+图像)"""
183   - print("\n" + "="*50)
184   - print("测试 5: 混合编码")
185   - print("="*50)
186   -
187   - try:
188   - # 准备混合数据
189   - mixed_data = [
190   - '这是一段测试文本',
191   - 'https://picsum.photos/224?random=10',
192   - 'CN-CLIP 图像编码',
193   - 'https://picsum.photos/224?random=11',
194   - ]
195   -
196   - print(f"混合数据数量: {len(mixed_data)}")
197   - print(f" 文本: 2 条")
198   - print(f" 图像: 2 条")
199   -
200   - # 执行编码
201   - start_time = time.time()
202   - embeddings = client.encode(mixed_data)
203   - elapsed_time = time.time() - start_time
204   -
205   - # 验证结果
206   - assert embeddings.shape[0] == len(mixed_data), "向量数量不匹配"
207   - assert embeddings.shape[1] == 1024, "向量维度应该是 1024"
208   -
209   - print_success(f"混合编码成功")
210   - print(f" 向量形状: {embeddings.shape}")
211   - print(f" 耗时: {elapsed_time:.2f}秒")
212   -
213   - return True
214   -
215   - except Exception as e:
216   - print_error(f"混合编码失败: {e}")
217   - return False
218   -
219   -
220   -def test_single_encoding(client):
221   - """测试单个数据编码"""
222   - print("\n" + "="*50)
223   - print("测试 6: 单个数据编码")
224   - print("="*50)
225   -
226   - try:
227   - # 测试单个文本
228   - single_text = '测试文本'
229   - print(f"输入: {single_text}")
230   -
231   - start_time = time.time()
232   - embedding = client.encode(single_text)
233   - elapsed_time = time.time() - start_time
234   -
235   - # 注意:单个数据会返回 (1, 1024) 的形状
236   - if embedding.ndim == 1:
237   - embedding = embedding.reshape(1, -1)
238   -
239   - assert embedding.shape == (1, 1024), f"向量形状应该是 (1, 1024), 实际是 {embedding.shape}"
240   -
241   - print_success(f"单个文本编码成功")
242   - print(f" 向量形状: {embedding.shape}")
243   - print(f" 耗时: {elapsed_time:.2f}秒")
244   -
245   - return True
246   -
247   - except Exception as e:
248   - print_error(f"单个数据编码失败: {e}")
249   - return False
250   -
251   -
252   -def main():
253   - parser = argparse.ArgumentParser(description='CN-CLIP 服务测试脚本')
254   - parser.add_argument('--url',
255   - default='grpc://localhost:51000',
256   - help='服务地址(默认:grpc://localhost:51000)')
257   - parser.add_argument('--text',
258   - action='store_true',
259   - help='只测试文本编码')
260   - parser.add_argument('--image',
261   - action='store_true',
262   - help='只测试图像编码')
263   - parser.add_argument('--batch-size',
264   - type=int,
265   - default=10,
266   - help='批处理大小(默认:10)')
267   -
268   - args = parser.parse_args()
269   -
270   - print("\n" + "="*50)
271   - print("CN-CLIP 服务测试")
272   - print("="*50)
273   -
274   - # 测试 1: 检查依赖
275   - if not test_imports():
276   - sys.exit(1)
277   -
278   - # 测试 2: 连接服务
279   - client = test_connection(args.url)
280   - if not client:
281   - sys.exit(1)
282   -
283   - # 运行测试
284   - results = []
285   -
286   - if args.text:
287   - # 只测试文本编码
288   - results.append(test_text_encoding(client, args.batch_size))
289   - elif args.image:
290   - # 只测试图像编码
291   - results.append(test_image_encoding(client, args.batch_size))
292   - else:
293   - # 运行所有测试
294   - results.append(test_text_encoding(client, args.batch_size))
295   - results.append(test_image_encoding(client, min(args.batch_size, 5)))
296   - results.append(test_mixed_encoding(client))
297   - results.append(test_single_encoding(client))
298   -
299   - # 汇总结果
300   - print("\n" + "="*50)
301   - print("测试结果汇总")
302   - print("="*50)
303   -
304   - total_tests = len(results)
305   - passed_tests = sum(results)
306   -
307   - print(f"总测试数: {total_tests}")
308   - print(f"通过: {passed_tests}")
309   - print(f"失败: {total_tests - passed_tests}")
310   -
311   - if passed_tests == total_tests:
312   - print_success("\n所有测试通过!")
313   - sys.exit(0)
314   - else:
315   - print_error("\n部分测试失败")
316   - sys.exit(1)
317   -
318   -
319   -if __name__ == '__main__':
320   - main()