Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<MilvusException: (code=65535, message=empty sparse float vector row)> #32972

Closed
1 task done
shilei4260 opened this issue May 11, 2024 · 31 comments
Closed
1 task done
Assignees
Labels
kind/bug Issues or changes related a bug stale indicates no udpates for 30 days triage/needs-information Indicates an issue needs more information in order to work on it.

Comments

@shilei4260
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Environment

- Milvus version:
- Deployment mode(standalone or cluster):
- MQ type(rocksmq, pulsar or kafka):    
- SDK version(e.g. pymilvus v2.0.0rc2):
- OS(Ubuntu or CentOS): 
- CPU/Memory: 
- GPU: 
- Others:

Current Behavior

稀疏和密集向量时出现的报错https://github.com/milvus-io/pymilvus/blob/master/examples/hello_hybrid_sparse_dense.py

Expected Behavior

No response

Steps To Reproduce

No response

Milvus Log

No response

Anything else?

No response

@shilei4260 shilei4260 added kind/bug Issues or changes related a bug needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels May 11, 2024
@yanliang567
Copy link
Contributor

@shilei4260 which version are you running for Milvus?
please offer milvus logs for investigation, thx
/assign @shilei4260
/unassign

@yanliang567 yanliang567 added triage/needs-information Indicates an issue needs more information in order to work on it. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels May 11, 2024
@xiaofan-luan
Copy link
Collaborator

what model you are using?
random or M3?

Copy link

stale bot commented Jun 11, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Rotten issues close after 30d of inactivity. Reopen the issue with /reopen.

@stale stale bot added the stale indicates no udpates for 30 days label Jun 11, 2024
@xxxfzxxx
Copy link

hi, I met a similar error. I use bm25 embedding function, I use encode_queries function: sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query]) but sparse_embedding is return empty. Why is that? the bm25_ef is def bm25_ef(self):
bm = BM25EmbeddingFunction(build_default_analyzer(language="zh"))
bm.load("bm25_params.json")
return bm. Note that my input query is "图片尺寸", I think bm25 tokenizer, aka, default_analyzer should split it to "图片" and "尺寸". I can find the ascii code for "图片" and "尺寸" in the bm25_params.json. I think the problem is the default analyzer does not tokenize my query.

@stale stale bot removed the stale indicates no udpates for 30 days label Jun 26, 2024
@xxxfzxxx
Copy link

urgent

@wxywb
Copy link
Contributor

wxywb commented Jun 26, 2024

@xxxfzxxx I'm checking this issue.

@wxywb
Copy link
Contributor

wxywb commented Jun 26, 2024

@xxxfzxxx your observation is correct.

from pymilvus.model.sparse.bm25.tokenizers import build_default_analyzer
from pymilvus.model.sparse import BM25EmbeddingFunction
import jieba

analyzer = build_default_analyzer(language="zh")

corpus = [
   "在登记册上所有的图片尺寸需要保持一致"
]

# analyzer can tokenize the text into tokens
tokens = analyzer(corpus[0])
print(analyzer.tokenizer.__dict__)
print("tokens:", tokens)
tokens: ['登记册', '', '图片尺寸', '保持一致']

The popular Chinese tokenizer project jieba used by this implementation will not split '图片尺寸' in two words. However jieba supports adjusting its vocabulary by user.
You can create a new file called custom.txt

图片 10000
尺寸 10000
from pymilvus.model.sparse.bm25.tokenizers import build_default_analyzer
from pymilvus.model.sparse import BM25EmbeddingFunction
import jieba

jieba.load_userdict("./custom.txt")
analyzer = build_default_analyzer(language="zh")

corpus = [
   "在登记册上所有的图片尺寸需要保持一致"
]

# analyzer can tokenize the text into tokens
tokens = analyzer(corpus[0])
print(analyzer.tokenizer.__dict__)
print("tokens:", tokens)
tokens: ['登记册', '', '图片', '尺寸', '保持一致']

@wxywb
Copy link
Contributor

wxywb commented Jun 26, 2024

Adjusting jieba vocab cannot handle all corner cases. At lease we could have a naive method.

from pymilvus.model.sparse.bm25.tokenizers import build_default_analyzer
from pymilvus.model.sparse import BM25EmbeddingFunction

class SimpleChineseTokenizer():
    def __init__(self):
        pass

    def tokenize(self, text: str):
        return list(text)


analyzer = build_default_analyzer(language="zh")
analyzer.tokenizer = SimpleChineseTokenizer()

corpus = [
   "在登记册上所有的图片尺寸需要保持一致"
]

# analyzer can tokenize the text into tokens
tokens = analyzer(corpus[0])
print(analyzer.tokenizer.__dict__)
print("tokens:", tokens)
tokens: ['', '', '', '', '', '', '', '', '', '', '', '']

@xxxfzxxx
Copy link

xxxfzxxx commented Jun 26, 2024

I wonder how the milvus builtin bm25embeddingFunction will embed the unseen word in the query? From my observation, it will give nothing(None). What is the best solution if the tokens in the query does not occur in the previous bm25 tokens dict?

@wxywb
Copy link
Contributor

wxywb commented Jun 26, 2024

@xxxfzxxx ,bm25 in this implementation will calculate the statistics(term frequencies, idfs) over tokenized words in documents. If a word tokenized in query not seem in documents then it would contribute nothing to the relevance score.
If you have such concerns, I think the best strategy is tokenizing Chinese sentences into single characters. For English, you need to tokenize them into subwords(like GPT's BPE tokens).

@wxywb
Copy link
Contributor

wxywb commented Jun 26, 2024

hi, I met a similar error. I use bm25 embedding function, I use encode_queries function: sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query]) but sparse_embedding is return empty. Why is that? the bm25_ef is def bm25_ef(self): bm = BM25EmbeddingFunction(build_default_analyzer(language="zh")) bm.load("bm25_params.json") return bm. Note that my input query is "图片尺寸", I think bm25 tokenizer, aka, default_analyzer should split it to "图片" and "尺寸". I can find the ascii code for "图片" and "尺寸" in the bm25_params.json. I think the problem is the default analyzer does not tokenize my query.

Do you mean you get a zero-size sparse embedding or a sparse embedding all with zeros(size equals your len(idf)).

@xiaofan-luan
Copy link
Collaborator

if corpus don't have this word, you will get 0 in this dimension.
becasue no corpus will match this word

@xxxfzxxx
Copy link

hi, I met a similar error. I use bm25 embedding function, I use encode_queries function: sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query]) but sparse_embedding is return empty. Why is that? the bm25_ef is def bm25_ef(self): bm = BM25EmbeddingFunction(build_default_analyzer(language="zh")) bm.load("bm25_params.json") return bm. Note that my input query is "图片尺寸", I think bm25 tokenizer, aka, default_analyzer should split it to "图片" and "尺寸". I can find the ascii code for "图片" and "尺寸" in the bm25_params.json. I think the problem is the default analyzer does not tokenize my query.

Do you mean you get a zero-size sparse embedding or a sparse embedding all with zeros(size equals your len(idf)).

Yes, I print the "图片尺寸“ sparse embedding and it output nothing. It should give me a csr matrix right?

@wxywb
Copy link
Contributor

wxywb commented Jun 27, 2024

hi, I met a similar error. I use bm25 embedding function, I use encode_queries function: sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query]) but sparse_embedding is return empty. Why is that? the bm25_ef is def bm25_ef(self): bm = BM25EmbeddingFunction(build_default_analyzer(language="zh")) bm.load("bm25_params.json") return bm. Note that my input query is "图片尺寸", I think bm25 tokenizer, aka, default_analyzer should split it to "图片" and "尺寸". I can find the ascii code for "图片" and "尺寸" in the bm25_params.json. I think the problem is the default analyzer does not tokenize my query.

Do you mean you get a zero-size sparse embedding or a sparse embedding all with zeros(size equals your len(idf)).

Yes, I print the "图片尺寸“ sparse embedding and it output nothing. It should give me a csr matrix right?

please show me your full code

@xxxfzxxx
Copy link

`dense_embeddings = [self.bgem3_model.get_embedding([query])[0]['dense_vecs']]
rewritten_query = self.get_query_rewrite(query)
sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query])
col = Collection(name=collection_name)
col.load()
search_param_dense = {
"data": dense_embeddings,
"anns_field": "dense_vector",
"param": {
"metric_type": "COSINE",
"params": {"nprobe": 10}
},
"limit": 100
}
search_param_sparse = {
"data": sparse_embeddings,
"anns_field": "sparse_vector",
"param": {
"metric_type": "IP",
"params": {"nprobe": 10}
},
"limit": 100 # TODO
}
request_dense = AnnSearchRequest(**search_param_dense)
request_sparse = AnnSearchRequest(**search_param_sparse)

    reqs = [request_dense, request_sparse]
    weighted_rerank = WeightedRanker(dense_weight, 1 - dense_weight)

    res = col.hybrid_search(
        reqs,
        weighted_rerank,
        limit=retrieved_cnt,
        output_fields=['doc_id', 'text', 'metadata']
    )`

hi, I met a similar error. I use bm25 embedding function, I use encode_queries function: sparse_embeddings = self.bm25_ef.encode_queries([rewritten_query]) but sparse_embedding is return empty. Why is that? the bm25_ef is def bm25_ef(self): bm = BM25EmbeddingFunction(build_default_analyzer(language="zh")) bm.load("bm25_params.json") return bm. Note that my input query is "图片尺寸", I think bm25 tokenizer, aka, default_analyzer should split it to "图片" and "尺寸". I can find the ascii code for "图片" and "尺寸" in the bm25_params.json. I think the problem is the default analyzer does not tokenize my query.

Do you mean you get a zero-size sparse embedding or a sparse embedding all with zeros(size equals your len(idf)).

Yes, I print the "图片尺寸“ sparse embedding and it output nothing. It should give me a csr matrix right?

please show me your full code

@wxywb
Copy link
Contributor

wxywb commented Jun 27, 2024

I wonder how you get the None sparse embedding.
https://github.com/milvus-io/milvus-model/blob/d812c9a84f2c530919ddffec8bf4024cce841e6b/milvus_model/sparse/bm25/bm25.py#L130
you can get a csr_array even you have an empty self.idf.

@xxxfzxxx
Copy link

I wonder how you get the None sparse embedding. https://github.com/milvus-io/milvus-model/blob/d812c9a84f2c530919ddffec8bf4024cce841e6b/milvus_model/sparse/bm25/bm25.py#L130 you can get a csr_array even you have an empty self.idf.

My bad, I check the type of the sparse_embeddings
print(">>>>", type(sparse_embeddings), sparse_embeddings) and the output is >>>> <class 'scipy.sparse._csr.csr_matrix'> meaning that sparse embedding is an csr_matrix. Since all values in the matrix are zeros so it does not print anything.

Then, how do I search for it, can you tell me how to update my hybrid search?
`search_param_dense = {
"data": dense_embeddings,
"anns_field": "dense_vector",
"param": {
"metric_type": "COSINE",
"params": {"nprobe": 10}
},
"limit": 100
}
search_param_sparse = {
"data": sparse_embeddings,
"anns_field": "sparse_vector",
"param": {
"metric_type": "IP",
"params": {"nprobe": 10}
},
"limit": 100 # TODO
}
request_dense = AnnSearchRequest(**search_param_dense)
request_sparse = AnnSearchRequest(**search_param_sparse)

    reqs = [request_dense, request_sparse]
    weighted_rerank = WeightedRanker(dense_weight, 1 - dense_weight)

    res = col.hybrid_search(
        reqs,
        weighted_rerank,
        limit=retrieved_cnt,
        output_fields=['doc_id', 'text', 'metadata']
    )`

raise MilvusException(status.code, status.reason, status.error_code)
pymilvus.exceptions.MilvusException: <MilvusException: (code=65535, message=fail to search on QueryNode 33: worker(33) query failed: Assert "size > 0" at /go/src/github.com/milvus-io/milvus/internal/core/src/common/Utils.h:227
=> Sparse row data should not be empty)>

@wxywb
Copy link
Contributor

wxywb commented Jun 27, 2024

@xxxfzxxx Your sparse embeddings seem to have zero length. Using following code to verify this.

print(sparse_embeddings.toarray().shape)

I think it will be a 0-length sparse embedding. Then you need to verify your bm25 idf, by.

print('elements in idf:', len(bm25_ef.idf))

It shouldn't be empty if you have fitted your corpus.

@xxxfzxxx
Copy link

(1, 18722)
elements in idf: 18722

@xxxfzxxx
Copy link

Note that the sparse_vector schema is FieldSchema(name="sparse_vector", dtype=DataType.SPARSE_FLOAT_VECTOR)

sparse_index = {"index_type": "SPARSE_INVERTED_INDEX", "metric_type": "IP"}

@xxxfzxxx
Copy link

xxxfzxxx commented Jun 27, 2024

@xxxfzxxx Your sparse embeddings seem to have zero length. Using following code to verify this.

print(sparse_embeddings.toarray().shape)

I think it will be a 0-length sparse embedding. Then you need to verify your bm25 idf, by.

print('elements in idf:', len(bm25_ef.idf))

It shouldn't be empty if you have fitted your corpus.

my query's sparse embeddings are not zero length. it is actually a all zero csr matrix.

@wxywb
Copy link
Contributor

wxywb commented Jun 27, 2024

Milvus's sparse embedding requires the number of non-zeros (nnz) in the sparse embedding(both the doc and the query) to be greater than 0. The users need to check the nnz of every row of the sparse embeddings before inserting/searching. When it equals zero, you need to fall back on dense retrieval.

sparse_embeddings.nnz # nnz of all rows of sparse_embeddings if sparse_embeddings contains multiple rows.
sparse_embeddings[0].nnz # nnz of the first row of sparse_embeddings.

The reason behind this is that as IP is the only available distance metric, an embedding with 0 non zero values will have a 0 IP distance to any other embeddings, thus a distance judgement cannot be made.

@wxywb
Copy link
Contributor

wxywb commented Jun 27, 2024

It seems that for the BM25EmbeddingFunction, there is a risk of generating an all-zero query sparse embedding, which is not supported by Milvus.

@xxxfzxxx
Copy link

xxxfzxxx commented Jul 1, 2024

I saw that https://github.com/milvus-io/milvus-model/blob/main/milvus_model/sparse/bm25/bm25.py line 194 has a json file to download(https://github.com/milvus-io/pymilvus-assets/releases/download/v0.1-bm25v1/bm25_msmarco_v1.json). But I cannot find it anywhere. Can you provide a chinese version?

@wxywb
Copy link
Contributor

wxywb commented Jul 1, 2024

It will download this file where you executed the code. Currently I only fitted the BM25EmbeddingFunction on MS MARCO dataset for English language. If you can fit it on your dataset, you will get better results. If you want a pretrained sparse embedding function for Chinese. I strongly recommend you to test this https://milvus.io/docs/embed-with-bgm-m3.md.

Copy link

stale bot commented Aug 4, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Rotten issues close after 30d of inactivity. Reopen the issue with /reopen.

@stale stale bot added the stale indicates no udpates for 30 days label Aug 4, 2024
@stale stale bot closed this as completed Aug 11, 2024
@wangyiran33
Copy link

wangyiran33 commented Sep 3, 2024

Can this PR solve the problem?

@xiaofan-luan
Copy link
Collaborator

I think an empty sparse float vector is a good signal, usually it means your corpus didn't fit the training dataset at all.
You should think of using another model like splade or m3

@wangyiran33
Copy link

I think an empty sparse float vector is a good signal, usually it means your corpus didn't fit the training dataset at all. You should think of using another model like splade or m3

I am using BM25, which has the advantage of low training costs. However, it inevitably leads to cases where user queries are not present in the corpus, resulting in empty sparse vector queries. In such cases of hybrid retrieval, sparse retrieval should return no results, while the hybrid result should be the result of dense retrieval, which may be better than throwing an error.

@xiaofan-luan
Copy link
Collaborator

make sense to me @zhengbuqian
what do you think?

@zhengbuqian
Copy link
Collaborator

make sense to me @zhengbuqian what do you think?

Yes. That is the expected behavior in Milvus with #34700, where empty sparse vectors are allowed.

Currently if the users insert the empty sparse vectors using python list sparse_vecs = [{}, {}], the PyMilvus SDK will not see sparse_vecs as sparse vectors, converting it to a csr_matrix should solve the issue. We are working on a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Issues or changes related a bug stale indicates no udpates for 30 days triage/needs-information Indicates an issue needs more information in order to work on it.
Projects
None yet
Development

No branches or pull requests

7 participants