String Tensors Basic Documentation (#22097)

* Basic documentation of how to use string tensors

* Fix typo: str_data -> bytes_data

* More clarification about UTF-8 and represenation capabilities

* Update docs/articles_en/openvino_workflow/running_inference_with_openvino/string_tensors.rst

Co-authored-by: Jan Iwaszkiewicz <jan.iwaszkiewicz@intel.com>

* Apply suggestions from code review

Co-authored-by: Karol Blaszczak <karol.blaszczak@intel.com>
Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>

* Apply suggestions from code review

Co-authored-by: Karol Blaszczak <karol.blaszczak@intel.com>

* Applied code review suggestion about 'floating-point'

* Update docs/articles_en/openvino_workflow/running_inference_with_openvino/string_tensors.rst

* Fixed a snippet tab rendering and items formatting in Additional Reasources section

---------

Co-authored-by: Jan Iwaszkiewicz <jan.iwaszkiewicz@intel.com>
Co-authored-by: Karol Blaszczak <karol.blaszczak@intel.com>
Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
This commit is contained in:
Sergey Lyalin 2024-01-17 19:02:16 +04:00 committed by GitHub
parent 10cae14374
commit 3dd41ff8c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 220 additions and 8 deletions

View File

@ -14,6 +14,7 @@ Running Inference with OpenVINO™
openvino_docs_OV_UG_ShapeInference
openvino_docs_OV_UG_DynamicShapes
openvino_docs_OV_UG_model_state_intro
openvino_docs_OV_UG_string_tensors
Optimize Inference <openvino_docs_deployment_optimization_guide_dldt_optimization_guide>
.. meta::
@ -21,7 +22,7 @@ Running Inference with OpenVINO™
OpenVINO Runtime is a set of C++ libraries with C and Python bindings providing a common API
to deploy inference on the platform of your choice. You can run any of the
to deploy inference on the platform of your choice. You can run any of the
:doc:`supported model formats <openvino_docs_model_processing_introduction>` directly or convert the model
and save it to the :doc:`OpenVINO IR <openvino_ir>` format, for maximum performance.
@ -38,7 +39,7 @@ OpenVINO IR provides by far the best first-inference latency scores.
For more detailed information on how to convert, read, and compile supported model formats
see the :doc:`Model Preparation article <openvino_docs_model_processing_introduction>`.
Note that TensorFlow models can be run using the
:doc:`torch.compile feature <pytorch_2_0_torch_compile>`, as well as the standard ways of
:doc:`converting TensorFlow <openvino_docs_OV_Converter_UG_prepare_model_convert_model_Convert_Model_From_PyTorch>`

View File

@ -16,12 +16,12 @@ Integrate OpenVINO™ with Your Application
.. meta::
:description: Learn how to implement a typical inference pipeline of OpenVINO™
:description: Learn how to implement a typical inference pipeline of OpenVINO™
Runtime in an application.
Following these steps, you can implement a typical OpenVINO™ Runtime inference
pipeline in your application. Before proceeding, make sure you have
Following these steps, you can implement a typical OpenVINO™ Runtime inference
pipeline in your application. Before proceeding, make sure you have
:doc:`installed OpenVINO Runtime <openvino_docs_install_guides_overview>` and set environment variables (run ``<INSTALL_DIR>/setupvars.sh`` for Linux or ``setupvars.bat`` for Windows, otherwise, the ``OpenVINO_DIR`` variable won't be configured properly to pass ``find_package`` calls).
@ -244,8 +244,8 @@ To learn how to change the device configuration, read the :doc:`Query device pro
Step 3. Create an Inference Request
###################################
``ov::InferRequest`` class provides methods for model inference in OpenVINO™ Runtime.
Create an infer request using the following code (see
``ov::InferRequest`` class provides methods for model inference in OpenVINO™ Runtime.
Create an infer request using the following code (see
:doc:`InferRequest detailed documentation <openvino_docs_OV_UG_Infer_request>` for more details):
.. tab-set::
@ -300,6 +300,7 @@ You can use external memory to create ``ov::Tensor`` and use the ``ov::InferRequ
:language: cpp
:fragment: [part4]
See :doc:`additional materials <openvino_docs_OV_UG_string_tensors>` to learn how to handle textual data as a model input.
Step 5. Start Inference
#######################
@ -330,7 +331,7 @@ OpenVINO™ Runtime supports inference in either synchronous or asynchronous mod
:fragment: [part5]
This section demonstrates a simple pipeline. To get more information about other ways to perform inference, read the dedicated
This section demonstrates a simple pipeline. To get more information about other ways to perform inference, read the dedicated
:doc:`"Run inference" section <openvino_docs_OV_UG_Infer_request>`.
Step 6. Process the Inference Results
@ -361,6 +362,7 @@ Go over the output tensors and process the inference results.
:language: cpp
:fragment: [part6]
See :doc:`additional materials <openvino_docs_OV_UG_string_tensors>` to learn how to handle textual data as a model output.
Step 7. Release the allocated objects (only for C)
##################################################
@ -441,5 +443,6 @@ Additional Resources
* See the :doc:`OpenVINO Samples <openvino_docs_OV_UG_Samples_Overview>` page or the `Open Model Zoo Demos <https://docs.openvino.ai/2023.3/omz_demos.html>`__ page for specific examples of how OpenVINO pipelines are implemented for applications like image classification, text prediction, and many others.
* :doc:`OpenVINO™ Runtime Preprocessing <openvino_docs_OV_UG_Preprocessing_Overview>`
* :doc:`String Tensors <openvino_docs_OV_UG_string_tensors>`
* :doc:`Using Encrypted Models with OpenVINO <openvino_docs_OV_UG_protecting_model_guide>`

View File

@ -0,0 +1,208 @@
.. {#openvino_docs_OV_UG_string_tensors}
String Tensors
==============
.. meta::
:description: Learn how to pass and retrieve text to and from OpenVINO model.
OpenVINO tensors can hold not only numerical data, like floating-point or integer numbers,
but also textual information, represented as one or multiple strings.
Such a tensor is called a string tensor and can be passed as input or retrieved as output of a text-processing model, such as
`tokenizers and detokenizers <https://github.com/openvinotoolkit/openvino_contrib/tree/master/modules/custom_operations/user_ie_extensions/tokenizer/python>`__.
While this section describes basic API to handle string tensors, more practical examples that leverage both
string tensors and OpenVINO tokenizer can be found in
`GenAI Samples <https://github.com/openvinotoolkit/openvino.genai/tree/master/text_generation/causal_lm/cpp>`__.
Representation
##############
String tensors are supported in C++ and Python APIs, represented as instances of the `ov::Tensor`
class with the `element_type` parameter equal to `ov::element::string`. Each element of a string tensor is a string
of arbitrary length, including an empty string, and can be set independently of other elements in the same tensor.
Depending on the API used (C++ or Python), the underlying data type that represents the string when accessing the tensor elements is
different:
- in C++, std::string is used
- in Python, `numpy.str_`/`numpy.bytes_` populated Numpy arrays are used, as a read-only copy of the underlying C++ content
String tensor implementation doesn't imply any limitations on string encoding, as underlying `std::string` doesn't have such limitations.
It is capable of representing all valid UTF-8 characters but also any other byte sequence outside of the UTF-8 encoding standard.
Users should pay extra attention when handling arbitrary byte sequences when accessing tensor content as encoded UTF-8 symbols.
As the string representation is more sophisticated in contrast to for example `float` or `int` data type,
the underlying memory that is used for string tensor representation cannot be handled without properly constructing and destroying string objects.
Also, in contrast to numerical data, C++ and Python do not share the same memory layout, so there is no immediate
sharing of tensor content between the two APIs. Python provides only a numpy-compatible view of the data
allocated and held in C++ core as an array of the `std::string` objects.
A developer must consider these restrictions when writing code using string tensors and
avoid treating the content as raw bytes or as a view of data in Python.
Create a String Tensor
######################
The following is an example of how to create a small 1D tensor pre-populated with three elements:
.. tab-set::
.. tab-item:: Python
:sync: py
.. code-block:: py
:force:
import openvino as ov
tensor = ov.Tensor(['text', 'more text', 'even more text'])
.. tab-item:: C++
:sync: cpp
.. code-block:: cpp
#include <vector>
#include <string>
#include <openvino/openvino.hpp>
std::vector<std::string> strings = {"text", "more text", "even more text"};
ov::Tensor tensor(ov::element::string, ov::Shape{strings.size()}, &strings[0]);
The example demonstrates that similarly to tensors with numerical information,
a tensor object can be created on top of existing memory in C++ by providing a pointer to a pre-allocated array of elements.
Here, an instance of std::vector is used to hold the memory and consists of three std::string objects.
So, the `tensor` object in the C++ example will share the same memory as the `strings` vector.
Note that `ov::Tensor`, when initialized with a pointer, requires pre-initialized memory with valid `std::string` objects
created by calling one of the available `std::string` constructors even for empty string. It is undefined behaviour if
not initialized memory is passed to this `ov::Tensor` constructor.
In the Python version of the example above, a regular list of strings is used as an initializer.
No memory sharing is available this time, in contrast to C++,
and the strings from the initialization list are copied to a separately allocated storage underneath the `tensor` object.
Besides a plain Python list of strings, an initializer can be one of the supported `numpy` arrays initialized
with Unicode or byte strings:
.. tab-set::
.. tab-item:: Python
:sync: py
.. code-block:: python
:force:
import numpy as np
tensor = ov.Tensor(np.array(['text', 'more text', 'even more text']))
tensor = ov.Tensor(np.array([b'text', b'more text', b'even more text']))
If `ov::Tensor` is created without providing initialization strings,
a tensor of a specified shape and empty strings as elements is created:
.. tab-set::
.. tab-item:: Python
:sync: py
.. code-block:: python
:force:
tensor = ov.Tensor(dtype=str, shape=[3])
.. tab-item:: C++
:sync: cpp
.. code-block:: cpp
ov::Tensor tensor(ov::element::string, ov::Shape{3});
`ov::Tensor` allocates and initializes the required number of `std::string` objects under the hood.
Accessing Elements
##################
The following code prints all elements in the 1D string tensor constructed above.
In C++ code the same `.data` template method is used for other data types,
and to access string data it should be called with the `std::string` type.
In Python, dedicated `std_data` and `byte_data` fields are used instead of `data` field for numerical data.
.. tab-set::
.. tab-item:: Python
:sync: py
.. code-block:: python
:force:
data = tensor.str_data # use tensor.byte_data instead to access encoded strings as `bytes`
for i in range(tensor.get_size()):
print(data[i])
.. tab-item:: C++
:sync: cpp
.. code-block:: cpp
#include <iostream>
std::string* data = tensor.data<std::string>();
for(size_t i = 0; i < tensor.get_size(); ++i)
std::cout << data[i] << '\n';
In the case of Python, an object retrieved with `tensor.str_data` (or `tensor.bytes_data`) is a numpy array
with `numpy.str_` elements (or `numpy.bytes_` correspondingly). It is a copy of underlying data from
the `tensor` object and cannot be used for tensor content modification.
To set new values, the entire tensor content should be set as a list or as a `numpy` array, as demonstrated
below.
In contrast to Python, when using `tensor.data<std::string>()` in C++, a pointer to the underlying data
storage is returned and it can be used for tensor element modification:
.. tab-set::
.. tab-item:: Python
:sync: py
.. code-block:: python
# Unicode strings:
tensor.str_data = ['one', 'two', 'three']
# Do NOT use tensor.str_data[i] to set a new value, it won't update the tensor content
# Encoded strings:
tensor.bytes_data = [b'one', b'two', b'three']
# Do NOT use tensor.bytes_data[i] to set a new value, it won't update the tensor content
.. tab-item:: C++
:sync: cpp
.. code-block:: cpp
std::string new_content[] = {"one", "two", "three"};
std::string* data = tensor.data<std::string>();
for(size_t i = 0; i < tensor.get_size(); ++i)
data[i] = new_content[i];
When reading or setting string tensor elements in Python, it is recommended to use `str` objects (or `numpy.str_` if used in numpy array)
when it is known that the underlying byte sequence forms a valid UTF-8 encoded string.
Otherwise, if arbitrary byte sequences are allowed,
not necessarily within the UTF-8 standard, use `bytes` strings (or `numpy.bytes_` correspondingly) instead.
Accessing tensor content through `str_data` implicitly applies UTF-8 decoding.
If parts of the byte stream cannot be represented as valid Unicode symbols,
the <20> replacement symbol is used to signal errors in such invalid Unicode streams.
Additional Resources
####################
* Learn about the :doc:`basic steps to integrate inference in your application <openvino_docs_OV_UG_Integrate_OV_with_your_application>`.
* Use `OpenVINO tokenizers <https://github.com/openvinotoolkit/openvino_contrib/tree/master/modules/custom_operations/user_ie_extensions/tokenizer/python>`__ to produce models that use string tensors to work with textual information as pre- and post-processing for the large language models.
* Check out `GenAI Samples <https://github.com/openvinotoolkit/openvino.genai/tree/master/text_generation/causal_lm/cpp>`__ to see how string tensors are used in real-life applications.