Using BERT model as a sentence encoding service, i.e. mapping a variable-length sentence to a fixed-length vector.
Highlights • What is it • Install • Getting Started • API • Tutorials • FAQ • Benchmark • Blog
BERT is a NLP model developed by Google for pre-training language representations. It leverages an enormous amount of plain text data publicly available on the web and is trained in an unsupervised manner. Pre-training a BERT model is a fairly expensive yet one-time procedure for each language. Fortunately, Google released several pre-trained models where you can download from here.
Sentence Encoding/Embedding is a upstream task required in many NLP applications, e.g. sentiment analysis, text classification. The goal is to represent a variable length sentence into a fixed length vector, e.g. hello world to [0.1, 0.3, 0.9]. Each element of the vector should "encode" some semantics of the original sentence.
Finally, bert-as-service uses BERT as a sentence encoder and hosts it as a service via ZeroMQ, allowing you to map sentences into fixed-length representations in just two lines of code.
More features: XLA & FP16 support; mix GPU-CPU workloads; optimized graph; tf.data friendly; customized tokenizer; flexible pooling strategy; build-in HTTP server and dashboard; async encoding; multicasting; etc.
Install the server and client via pip. They can be installed separately or even on different machines:
pip install bert-serving-server # server
pip install bert-serving-client # client, independent of `bert-serving-server`
Note that the server MUST be running on Python >= 3.5 with Tensorflow >= 1.10 (one-point-ten). Again, the server does not support Python 2!
:point_up: The client can be running on both Python 2 and 3 for the following consideration.
Download a model listed below, then uncompress the zip file into some folder, say /tmp/english_L-12_H-768_A-12/
| BERT-Base, Uncased | 12-layer, 768-hidden, 12-heads, 110M parameters |
| BERT-Large, Uncased | 24-layer, 1024-hidden, 16-heads, 340M parameters |
| BERT-Base, Cased | 12-layer, 768-hidden, 12-heads , 110M parameters |
| BERT-Large, Cased | 24-layer, 1024-hidden, 16-heads, 340M parameters |
| BERT-Base, Multilingual Cased (New) | 104 languages, 12-layer, 768-hidden, 12-heads, 110M parameters |
| BERT-Base, Multilingual Cased (Old) | 102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters |
| BERT-Base, Chinese | Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters |
Optional: fine-tuning the model on your downstream task. Why is it optional?
After installing the server, you should be able to use bert-serving-start CLI as follows:
bert-serving-start -model_dir /tmp/english_L-12_H-768_A-12/ -num_worker=4
This will start a service with four workers, meaning that it can handle up to four concurrent requests. More concurrent requests will be queued in a load balancer. Details can be found in our FAQ and the benchmark on number of clients.
Below shows what the server looks like when starting correctly:

docker build -t bert-as-service -f ./docker/Dockerfile .
NUM_WORKER=1
PATH_MODEL=/PATH_TO/_YOUR_MODEL/
docker run --runtime nvidia -dit -p 5555:5555 -p 5556:5556 -v $PATH_MODEL:/model -t bert-as-service $NUM_WORKER
Now you can encode sentences simply as follows:
from bert_serving.client import BertClient
bc = BertClient()
bc.encode(['First do it', 'then do it right', 'then do it better'])
It will return a ndarray (or List[List[float]] if you wish), in which each row is a fixed-length vector representing a sentence. Having thousands of sentences? Just encode! Don't even bother to batch, the server will take care of it.
As a feature of BERT, you may get encodes of a pair of sentences by concatenating them with ||| (with whitespace before and after), e.g.
bc.encode(['First do it ||| then do it right'])
Below shows what the server looks like while encoding:

One may also start the service on one (GPU) machine and call it from another (CPU) machine as follows:
# on another CPU machine
from bert_serving.client import BertClient
bc = BertClient(ip='xx.xx.xx.xx') # ip address of the GPU machine
bc.encode(['First do it', 'then do it right', 'then do it better'])
Note that you only need pip install -U bert-serving-client in this case, the server side is not required. You may also call the service via HTTP requests.
:bulb: Want to learn more? Checkout our tutorials:
- Building a QA semantic search engine in 3 min.
- Serving a fine-tuned BERT model
- Getting ELMo-like contextual word embedding
- Using your own tokenizer
- Using
BertClientwithtf.dataAPI- Training a text classifier using BERT features and tf.estimator API
- Saving and loading with TFRecord data
- Asynchronous encoding
- Broadcasting to multiple clients
- Monitoring the service status in a dashboard
- Using
bert-as-serviceto serve HTTP requests in JSON- Starting
BertServerfrom Python
The best way to learn bert-as-service latest API is reading the documentation.
Please always refer to the latest server-side API documented here., you may get the latest usage via:
bert-serving-start --help
bert-serving-terminate --help
bert-serving-benchmark --help
| Argument | Type | Default | Description |
|---|---|---|---|
model_dir | str | Required | folder path of the pre-trained BERT model. |
tuned_model_dir | str | (Optional) | folder path of a fine-tuned BERT model. |
ckpt_name | str | bert_model.ckpt | filename of the checkpoint file. |
config_name | str | bert_config.json | filename of the JSON config file for BERT model. |
graph_tmp_dir | str | None | path to graph temp file |
max_seq_len | int | 25 | maximum length of sequence, longer sequence will be trimmed on the right side. Set it to NONE for dynamically using the longest sequence in a (mini)batch. |
cased_tokenization | bool | False | Whether tokenizer should skip the default lowercasing and accent removal. Should be used for e.g. the multilingual cased pretrained BERT model. |
mask_cls_sep | bool | False | masking the embedding on [CLS] and [SEP] with zero. |
num_worker | int | 1 | number of (GPU/CPU) worker runs BERT model, each works in a separate process. |
max_batch_size | int | 256 | maximum number of sequences handled by each worker, larger batch will be partitioned into small batches. |
priority_batch_size | int | 16 | batch smaller than this size will be labeled as high priority, and jumps forward in the job queue to get result faster |
port | int | 5555 | port for pushing data from client to server |
port_out | int | 5556 | port for publishing results from server to client |
http_port | int | None | server port for receiving HTTP requests |
cors | str | * | setting "Access-Control-Allow-Origin" for HTTP requests |
pooling_strategy | str | REDUCE_MEAN | the pooling strategy for generating encoding vectors, valid values are NONE, REDUCE_MEAN, REDUCE_MAX, REDUCE_MEAN_MAX, CLS_TOKEN, FIRST_TOKEN, SEP_TOKEN, LAST_TOKEN. Explanation of these strategies can be found here. To get encoding for each token in the sequence, please set this to NONE. |
pooling_layer | list | [-2] | the encoding layer that pooling operates on, where -1 means the last layer, -2 means the second-to-last, [-1, -2] means concatenating the result of last two layers, etc. |
gpu_memory_fraction | float | 0.5 | the fraction of the overall amount of memory that each GPU should be allocated per worker |
cpu | bool | False | run on CPU instead of GPU |
xla | bool | False | enable XLA compiler for graph optimization (experimental!) |
fp16 | bool | False | use float16 precision (experimental) |
device_map | list | [] | specify the list of GPU device ids that will be used (id starts from 0) |
show_tokens_to_client | bool | False | sending tokenization results to client |
Please always refer to the latest client-side API documented here. Client-side provides a Python class called BertClient, which accepts arguments as follows:
| Argument | Type | Default | Description |
|---|---|---|---|
ip | str | localhost | IP address of the server |
port | int | 5555 | port for pushing data from client to server, must be consistent with the server side config |
port_out | int | 5556 | port for publishing results from server to client, must be consistent with the server side config |
output_fmt | str | ndarray | the output format of the sentence encodes, either in numpy array or python List[List[float]] (ndarray/list) |
show_server_config | bool | False | whether to show server configs when first connected |
check_version | bool | True | whether to force client and server to have the same version |
identity | str | None | a UUID that identifies the client, useful in multi-casting |
timeout | int | -1 | set the timeout (milliseconds) for receive operation on the client |
A BertClient implements the following methods and properties:
| Method | Description |
|---|---|
.encode() | Encode a list of strings to a list of vectors |
.encode_async() | Asynchronous encode batches from a generator |
.fetch() | Fetch all encoded vectors from server and return them in a generator, use it with .encode_async() or .encode(blocking=False). Sending order is NOT preserved. |
.fetch_all() | Fetch all encoded vectors from server and return them in a list, use it with .encode_async() or .encode(blocking=False). Sending order is preserved. |
.close() | Gracefully close the connection between the client and the server |
.status | Get the client status in JSON format |
.server_status | Get the server status in JSON format |
The full list of examples can be found in example/. You can run each via python example/example-k.py. Most of examples require you to start a BertServer first, please follow the instruction here. Note that although BertClient works universally on both Python 2.x and 3.x, examples are only tested on Python 3.6.
- Building a QA semantic search engine in 3 min.
- Serving a fine-tuned BERT model
- Getting ELMo-like contextual word embedding
- Using your own tokenizer
- Using
BertClientwithtf.dataAPI- Training a text classifier using BERT features and tf.estimator API
- Saving and loading with TFRecord data
- Asynchronous encoding
- Broadcasting to multiple clients
- Monitoring the service status in a dashboard
- Using
bert-as-serviceto serve HTTP requests in JSON- Starting
BertServerfrom Python
The complete example can be found example8.py.
As the first example, we will implement a simple QA search engine using bert-as-service in just three minutes. No kidding! The goal is to find similar questions to user's input and return the corresponding answer. To start, we need a list of question-answer pairs. Fortunately, this README file already contains a list of FAQ, so I will just use that to make this example perfectly self-contained. Let's first load all questions and show some statistics.
prefix_q = '##### **Q:** '
with open('README.md') as fp:
questions = [v.replace(prefix_q, '').strip() for v in fp if v.strip() and v.startswith(prefix_q)]
print('%d questions loaded, avg. len of %d' % (len(questions), np.mean([len(d.split()) for d in questions])))
This gives 33 questions loaded, avg. len of 9. So looks like we have enough questions. Now start a BertServer with uncased_L-12_H-768_A-12 pretrained BERT model:
bert-serving-start -num_worker=1 -model_dir=/data/cips/data/lab/data/model/uncased_L-12_H-768_A-12
Next, we need to encode our questions into vectors:
bc = BertClient(port=4000, port_out=4001)
doc_vecs = bc.encode(questions)
Finally, we are ready to receive new query and perform a simple "fuzzy" search against the existing questions. To do that, every time a new query is coming, we encode it as a vector and compute its dot product with doc_vecs; sort the result descendingly; and return the top-k similar questions as follows:
while True:
query = input('your question: ')
query_vec = bc.encode([query])[0]
# compute normalized dot product as score
score = np.sum(query_vec * doc_vecs, axis=1) / np.linalg.norm(doc_vecs, axis=1)
topk_idx = np.argsort(score)[::-1][:topk]
for idx in topk_idx:
print('> %s\t%s' % (score[idx], questions[idx]))
That's it! Now run the code and type your query, see how this search engine handles fuzzy match:

Pretrained BERT models often show quite "okayish" performance on many tasks. However, to release the true power of BERT a fine-tuning on the downstream task (or on domain-specific data) is necessary. In this example, I will show you how to serve a fine-tuned BERT model.
We follow the instruction in "Sentence (and sentence-pair) classification tasks" and use run_classifier.py to fine tune uncased_L-12_H-768_A-12 model on MRPC task. The fine-tuned model is stored at /tmp/mrpc_output/, which can be changed by specifying --output_dir of run_classifier.py.
If you look into /tmp/mrpc_output/, it contains something like:
checkpoint 128
eval 4.0K
eval_results.txt 86
eval.tf_record 219K
events.out.tfevents.1545202214.TENCENT64.site 6.1M
events.out.tfevents.1545203242.TENCENT64.site 14M
graph.pbtxt 9.0M
model.ckpt-0.data-00000-of-00001 1.3G
model.ckpt-0.index 23K
model.ckpt-0.meta 3.9M
model.ckpt-343.data-00000-of-00001 1.3G
model.ckpt-343.index 23K
model.ckpt-343.meta 3.9M
train.tf_record 2.0M
Don't be afraid of those mysterious files, as the only important one to us is model.ckpt-343.data-00000-of-00001 (looks like my training stops at the 343 step. One may get model.ckpt-123.data-00000-of-00001 or model.ckpt-9876.data-00000-of-00001 depending on the total training steps). Now we have collected all three pieces of information that are needed for serving this fine-tuned model:
/path/to/bert/uncased_L-12_H-768_A-12/tmp/mrpc_output/;model.ckpt-343 something something.Now start a BertServer by putting three pieces together:
bert-serving-start -model_dir=/pretrained/uncased_L-12_H-768_A-12 -tuned_model_dir=/tmp/mrpc_output/ -ckpt_name=model.ckpt-343
After the server started, you should find this line in the log:
I:GRAPHOPT:[gra:opt: 50]:checkpoint (override by fine-tuned model): /tmp/mrpc_output/model.ckpt-343
Which means the BERT parameters is overrode and successfully loaded from our fine-tuned /tmp/mrpc_output/model.ckpt-343. Done!
In short, find your fine-tuned model path and checkpoint name, then feed them to -tuned_model_dir and -ckpt_name, respectively.
Start the server with pooling_strategy set to NONE.
bert-serving-start -pooling_strategy NONE -model_dir /tmp/english_L-12_H-768_A-12/
To get the word embedding corresponds to every token, you can simply use slice index as follows:
# max_seq_len = 25
# pooling_strategy = NONE
bc = BertClient()
vec = bc.encode(['hey you', 'whats up?'])
vec # [2, 25, 768]
vec[0] # [1, 25, 768], sentence embeddings for `hey you`
vec[0][0] # [1, 1, 768], word embedding for `[CLS]`
vec[0][1] # [1, 1, 768], word embedding for `hey`
vec[0][2] # [1, 1, 768], word embedding for `you`
vec[0][3] # [1, 1, 768], word embedding for `[SEP]`
vec[0][4] # [1, 1, 768], word embedding for padding symbol
vec[0][25] # error, out of index!
Note that no matter how long your original sequence is, the service will always return a [max_seq_len, 768] matrix for every sequence. When using slice index to get the word embedding, beware of the special tokens padded to the sequence, i.e. [CLS], [SEP], 0_PAD.
Often you want to use your own tokenizer to segment sentences instead of the default one from BERT. Simply call encode(is_tokenized=True) on the client slide as follows:
texts = ['hello world!', 'good day']
# a naive whitespace tokenizer
texts2 = [s.split() for s in texts]
vecs = bc.encode(texts2, is_tokenized=True)
This gives [2, 25, 768] tensor where the first [1, 25, 768] corresponds to the token-level encoding of "hello world!". If you look into its values, you will find that only the first four elements, i.e. [1, 0:3, 768] have values, all the others are zeros. This is due to the fact that BERT considers "hello world!" as four tokens: [CLS] hello world! [SEP], the rest are padding symbols and are masked out before output.
Note that there is no need to start a separate server for handling tokenized/untokenized sentences. The server can tell and handle both cases automatically.
Sometimes you want to know explicitly the tokenization performed on the server side
Content type
Image
Digest
Size
1.7 GB
Last updated
over 6 years ago
docker pull deliah/bert-as-service