English | ไธญๆ
ไธไธชๅบไบๅค่ฟ็จๆถๆ็้ซๆง่ฝ Embedding ๆๅกๅจ๏ผไธ้จไธบ่งฃๅณ CPU tokenizer ็ถ้ขๅๆๅคงๅ GPU ๅฉ็จ็่่ฎพ่ฎกใ
ๆ ธๅฟ็นๆง๏ผ
- ๆฏๆ Flash Attention ๅ FlashInfer ๅ ้ๆณจๆๅ่ฎก็ฎ
- ๆฏๆ tensor parallel
- ๅค่ฟ็จๆถๆๅฎๅ จ็ช็ ด Python GIL ้ๅถ
- ไธไธบ Embedding ๅบๆฏไผๅ็่ฝป้็บงๆจ็ๅผๆ
- ๆบ่ฝๅจๆ batch ่ๅ๏ผๆๅคงๅ GPU ๅๅ
- ่ชๅจๆจกๅๆถๆ่ฏๅซ๏ผๆฏๆ Qwen2 ๅ Qwen3 ็ณปๅๆจกๅ
ๆฏๆ็ๆจกๅๆถๆ๏ผ
- Qwen2ForCausalLM๏ผQwen2 ็ณปๅๆจกๅ๏ผๅฆ
Qwen/Qwen2-0.5BใQwen/Qwen2-1.5B็ญ๏ผ- Qwen3ForCausalLM๏ผQwen3 ็ณปๅๆจกๅ๏ผๅฆ
Qwen/Qwen3-Embedding-0.6B็ญ๏ผ็ณป็ปไผๆ นๆฎๆจกๅ้ ็ฝฎ็
architecturesๅญๆฎต่ชๅจ้ๆฉๅฏนๅบ็ๆจกๅๅฎ็ฐ
# ๅ
้ไปๅบ
git clone https://github.com/woodx9/minimal-embedding-server.git
cd minimal-embedding-server
pip install -e .ๆณจๆ๏ผๅฎ่ฃ ่ฟ็จไผ่ชๅจไธ่ฝฝๅนถๅฎ่ฃ ๏ผ
- PyTorch 2.9.1 (CUDA 12.8)
- SGL-Kernel 0.3.21
- FlashInfer 0.6.2
- ๅ ถไปไพ่ตๅ
# ๅฏๅจ Qwen3 Embedding ๆจกๅ
mes --model "Qwen/Qwen3-Embedding-0.6B"
# ๅฏๅจ Qwen2 ๆจกๅ
mes --model "Qwen/Qwen2-0.5B"
# ๆๅฎ็ซฏๅฃๅๆณจๆๅๅ็ซฏ
mes --model "Qwen/Qwen3-Embedding-0.6B" --port 8000 --attn-backend flash_attn
# ไฝฟ็จไธๅๆฐๆฎ็ฑปๅ
mes --model "Qwen/Qwen2-1.5B" --dtype bfloat16
# ๅคGPUๅนถ่กๆจ็
mes --model "Qwen/Qwen3-Embedding-0.6B" --tensor_parallel_size 2 --dtype auto
# ๆฅ็ๆดๅค้้กน
mes --helpๅฝไปค่กๅๆฐ๏ผ
| ๅๆฐ | ้ป่ฎคๅผ | ่ฏดๆ |
|---|---|---|
--model |
ๅฟ ้ | ๆจกๅๅ็งฐๆ่ทฏๅพ๏ผๅฟ ้ๅๆฐ๏ผ |
--host |
0.0.0.0 |
ๆๅกๅจ็ๅฌๅฐๅ |
--port |
8000 |
ๆๅกๅจ็ๅฌ็ซฏๅฃ |
--attn-backend |
flash_attn |
ๆณจๆๅๅ็ซฏ๏ผflash_attn/flash_infer๏ผ |
--tensor_parallel_size |
1 |
ๅผ ้ๅนถ่ก size |
--dtype |
auto |
ๆจกๅๆฐๆฎ็ฑปๅ๏ผauto/float32/float16/bfloat16๏ผ |
# ๅฅๅบทๆฃๆฅ
curl http://localhost:8000/health
# ่ทๅ embeddings
curl -X POST http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-Embedding-0.6B",
"input": ["ไฝ ๅฅฝ๏ผไธ็๏ผ", "Hello, world!"]
}'
ๅๆตๅฏนๆฏ๏ผ10 ๅนถๅๅฎขๆท็ซฏ๏ผๆฏๆน 20 ไธชๆๆฌ๏ผๆฏๆๆฌ 1000 tokens๏ผ
| ๆกๆถ | QPS | ๆง่ฝๆๅ |
|---|---|---|
| vLLM | 1.04 | ๅบๅ |
| ๆฌๆกๆถ | 1.10 | ๅฟซ 5.8% |
ๆต่ฏๅฝไปค๏ผ
python3 benchmark/stress_test.py \
--concurrent-clients 10 \
--batch-size 20 \
--token-length 1000 \
--base-url http://localhost:8000 \
--model Qwen/Qwen3-Embedding-0.6Bๆต่ฏ่ๆฌๅ้จ็ฝฒ่ๆฌไฝไบ
benchmark/็ฎๅฝไธ๏ผ
stress_test.py- ๆง่ฝๅๆต่ๆฌvllm.sh- vLLM ้จ็ฝฒ่ๆฌcompare_transformers.py- ๅฏนๆฏ transformer ้ๅบฆ่ๆฌ
ไธบไปไนๆดๅฟซ๏ผ
ๆฌๆกๆถไธไธบ Embedding ๅบๆฏ่ฎพ่ฎก๏ผๆดๅ ็ฒพ็ฎ้ซๆ๏ผ
- ๅป้คไบ vLLM ไธญๅคๆ็้็จ LLM ๆจ็้ป่พ๏ผ้ๆ ทใ่งฃ็ ใKV Cache ็ญ๏ผ
- ้ๅฏน Embedding ไปปๅกไผๅ็่ฝป้็บงๆถๆ
- ๅค่ฟ็จ้็ฆป๏ผCPU tokenizer ๅ GPU ๆจ็ๅฎๅ จๅนถ่ก
- ๆบ่ฝๅจๆ batch ่ๅ๏ผๆๅคงๅ GPU ๅๅ
- ๅ้ๅๅๅค็๏ผๅๆฌก GPU ๅๆญฅไปฃๆฟๅคๆฌกๅๆญฅ
ๅจไผ ็ป็ๅ่ฟ็จๆจ็ๆๅกไธญ๏ผ็ปๅธธ้ๅฐไปฅไธ้ฎ้ข๏ผ
- CPU ๅฉ็จ็ๆดๆถจ่ณ 400%๏ผๅค็บฟ็จ tokenizer ๅ GIL ้ๅถ๏ผ
- GPU ๅฉ็จ็ไธ้๏ผtokenizer ้ปๅกๅฏผ่ด GPU ้ฅฅ้ฅฟ๏ผ
- ๆจ็ๅปถ่ฟๅขๅ ๏ผCPU ๅ GPU ๆ ๆณๅนถ่กๅทฅไฝ๏ผ
ๆฌๆกๆถ้่ฟๅค่ฟ็จๆถๆๅฝปๅบ่งฃๅณ่ฟไบ้ฎ้ข๏ผๅฎ็ฐ CPU ๅ GPU ็ๅฎๅ จๅนถ่กใ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FastAPI Server โ
โ (uvicorn + asyncio) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Engine (ไธปๅ่ฐๅจ) โ
โ - ๅๅปบ MPQueue ่ฟ่ก่ฟ็จ้ด้ไฟก โ
โ - ๅฏๅจ Tokenizer Manager ่ฟ็จ โ
โ - ๅฏๅจ GPU Worker ่ฟ็จ โ
โ - ็ปๆๅๅ็บฟ็จ๏ผResult Dispatcher๏ผ โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Tokenizer Manager ่ฟ็จ โ โ GPU Worker ่ฟ็จ โ
โ (CPU ๅฏ้ๅ) โ โ (GPU ๅฏ้ๅ) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โข 10 ไธช Tokenizer ็บฟ็จ โ โ โข ๆจกๅๅ ่ฝฝๅฐ GPU โ
โ โข 1 ไธช Batch Prepare ็บฟ็จ โโโโโโโโถโ โข 1 ไธช Inference ็บฟ็จ โ
โ โข CPU ไธๅฎๆๆๆ tokenize โ โ โข 4 ไธช Callback ็บฟ็จ โ
โ โข ๅจๆ batch ่ๅ โ โ โข ๅ้ๅๅๅค็ โ
โ โข numpy ๅบๅๅไผ ่พ โ โ โข ๆน้ๅฝไธๅ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
้ฎ้ข๏ผ Python GIL ๅฏผ่ดๅค็บฟ็จ tokenizer ๆ ๆณ็ๆญฃๅนถ่ก๏ผCPU ้ฃๅไฝๆ็ไฝไธใ
่งฃๅณๆนๆก๏ผ
# Tokenizer Manager - ็ฌ็ซ่ฟ็จ
_prepare_process = Process(target=tokenizer_manager_main)
# GPU Worker - ็ฌ็ซ่ฟ็จ
_inference_process = Process(target=gpu_worker_main)ๆๆ๏ผ
- Tokenizer ๅ GPU ๆจ็ๅจไธๅ่ฟ็จไธญ่ฟ่ก
- ๅฎๅ จ้ฟๅผ GIL ้ๅถ
- CPU ๅ GPU ็ๆญฃๅนถ่กๅทฅไฝ
ๆ ธๅฟ็ญ็ฅ๏ผ
# ๅจๆ็ญๅพ
็ญ็ฅ
max_wait_rounds = 1 if ready_queue.qsize() < 3 else 10
while total_tokens < max_tokens_per_batch:
# 1. ๅฟซ้ๆถ้้ๅไธญๆๆ็ญๅพ
่ฏทๆฑ
while not tokenized_queue.empty():
batch.append(tokenized_queue.get_nowait())
# 2. ๆ นๆฎ GPU ่ด่ฝฝๅจๆ่ฐๆด็ญๅพ
ๆถ้ด
# GPU ็ฉบ้ฒๆถๅฟซ้ๅ้๏ผGPU ๅฟๆถๆฟ่ฟ่ๅไผๅฟ๏ผ
- GPU ็ฉบ้ฒๆถ๏ผ็ซๅณๅ้ๅฐ batch๏ผ้ไฝๅปถ่ฟ
- GPU ็นๅฟๆถ๏ผ็ญๅพ ๆดๅค่ฏทๆฑ๏ผ่ๅๆๅคง batch
- Token ไธ้๏ผ
max_tokens_per_batch = 120,000๏ผๅ ๅๅฉ็จ GPU ๆพๅญ
ไผ ็ปๆนๆณ็้ฎ้ข๏ผ
# ๆงไปฃ็ ๏ผๅคๆฌก GPU ๅๆญฅ๏ผๆง่ฝๅทฎ
for seq_len in seq_lengths:
embedding = outputs[start:start+seq_len][-1] # GPU ๆไฝ
embedding = F.normalize(embedding) # GPU ๆไฝ
embeddings.append(embedding.cpu()) # GPUโCPU ๅๆญฅ
start += seq_lenไผๅๅ็ๅ้ๅๅค็๏ผ
# ๆฐไปฃ็ ๏ผๅๆฌก GPU ๅๆญฅ๏ผๆง่ฝๆๅ 10 ๅ
# 1. ้ข่ฎก็ฎๆๆ last token ็ดขๅผ๏ผCPU ไธๅฎๆ๏ผ
last_token_indices = [idx + seq_len - 1 for idx, seq_len in ...]
# 2. ไธๆฌกๆงๆๅๆๆ embeddings๏ผGPU ๅ้ๅๆไฝ๏ผ
last_token_indices_tensor = torch.tensor(last_token_indices, device='cuda')
all_embeddings = outputs[last_token_indices_tensor] # [N, hidden_dim]
# 3. ๆน้ๅฝไธๅ๏ผGPU ๅ้ๅๆไฝ๏ผ
all_embeddings = F.normalize(all_embeddings, p=2, dim=1)
# 4. ๅๆฌก่ฝฌ CPU๏ผๅชๆไธๆฌก GPU ๅๆญฅ๏ผ๏ผ
all_embeddings_cpu = all_embeddings.cpu()1. ็จๆท่ฏทๆฑ
POST /v1/embeddings {"input": ["text1", "text2"]}
โ
โผ
2. Engine.v1_embeddings (ไธป่ฟ็จ)
- ็ๆ UUID ไฝไธบ future_id
- ๅญๅจๅฐ _future_map: {uuid: (future, num_texts)}
- ๅ้ๅฐ raw_request_queue: (texts, future_id)
โ
โผ
3. Tokenizer Manager ่ฟ็จ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Tokenizer ็บฟ็จๆฑ (10 ็บฟ็จ) โ
โ - ๅนถ่ก tokenize ๅคไธช่ฏทๆฑ โ
โ - CPU ๅฏ้ๅๆไฝ๏ผๅฎๅ
จๅนถ่ก โ
โ โ tokenized_queue โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Batch Prepare ็บฟ็จ (1 ็บฟ็จ) โ
โ - ๆฟ่ฟ่ๅ๏ผๆถ้ๅคไธช่ฏทๆฑ โ
โ - ๅจๆ็ญๅพ
็ญ็ฅ โ
โ - ้ข่ฎก็ฎ last_token_indices โ
โ - ่ฝฌ numpy ๅๅค่ทจ่ฟ็จไผ ่พ โ
โ โ ready_inference_queue โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
4. GPU Worker ่ฟ็จ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Inference ็บฟ็จ (1 ็บฟ็จ) โ
โ - numpy โ tensor โ GPU โ
โ - ๆจกๅๆจ็ โ
โ - ๅ้ๅๅๅค็ (ๅๆฌก GPU ๅๆญฅ) โ
โ โ callback_queue โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Callback ็บฟ็จๆฑ (4 ็บฟ็จ) โ
โ - ๅผๆญฅๅ้็ปๆ โ
โ โ result_queue (่ทจ่ฟ็จ) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
5. Engine ็ปๆๅๅ็บฟ็จ (ไธป่ฟ็จ)
- ไป result_queue ๆฅๆถ็ปๆ
- ๆ นๆฎ num_texts ๆญฃ็กฎๅๅฒ embeddings
- ้่ฟ future.set_result() ่ฟๅ็ปๅฏนๅบ่ฏทๆฑ
โ
โผ
6. ่ฟๅ็ป็จๆท
{"data": [{"embedding": [...], "index": 0}, ...]}
# UUID ไฟ่ฏๅฏไธๆง๏ผๆ ้้ไฟๆค
future_id = str(uuid.uuid4())
# GIL ไฟ่ฏๅไธช่ตๅผ็ๅๅญๆง
self._future_map[future_id] = (future, len(input))
# ๅชๅจ็ปๅๆไฝ๏ผcheck + read + delete๏ผๆถๅ ้
with self._future_lock:
if future_id in self._future_map:
future, num_texts = self._future_map[future_id]
del self._future_map[future_id]# ไฝฟ็จ multiprocessing.Queue ่ฟ่ก่ฟ็จ้ด้ไฟก
raw_request_queue = MPQueue(maxsize=1000)
ready_inference_queue = MPQueue(maxsize=100)
result_queue = MPQueue(maxsize=1000)
# tensor ่ฝฌ numpy ๆนไพฟๅบๅๅไผ ่พ
merged_input_ids.numpy() # ๅจ Tokenizer Manager
torch.from_numpy(input_ids_np).to('cuda') # ๅจ GPU Worker# ๆ นๆฎ GPU ้ๅๆทฑๅบฆๅจๆ่ฐๆด็ญๅพ
็ญ็ฅ
if ready_queue.qsize() < 3:
max_wait_rounds = 1 # GPU ็ฉบ้ฒ๏ผๅฟซ้ๅ้
else:
max_wait_rounds = 10 # GPU ็นๅฟ๏ผๆฟ่ฟ่ๅ
# ๆ็ปญๆถ้็ดๅฐ token ไธ้ๆ่ถ
ๆถ
while total_tokens < 120000 and wait_rounds < max_wait_rounds:
# ๅฟซ้ๆถ้ + ่ถ
ๆถ็ญๅพ
MIT License
ๆฌ้กน็ฎไธไธบ่งฃๅณๅฎ้ ็ไบง็ฏๅขไธญ็ GPU ๅฉ็จ็้ฎ้ข่่ฎพ่ฎก๏ผ้็จไบๅค้กนไธ็ๆไฝณๅฎ่ทตใ