OpenVINO™ Python API 限定機能

OpenVINO™ ランタイム Python API は、ユーザー・エクスペリエンスを向上させる追加機能とヘルパーを提供します。Python API の主な目標は、Python ユーザーに使いやすく、シンプルでありながら強力なツールを提供することです。

モデルのコンパイルがより簡単に

CompiledModel ヘルパーメソッドを使えば簡単に作成できます。Core の作成を隠匿し、デフォルトで AUTO 推論モードを適用します。

compiled_model = ov.compile_model(model)

モデル/コンパイル済みモデルの入力と出力

C++ API に適合した関数に加えて、一部の関数には Python 対応または拡張機能があります。例えば、Model および CompiledModel の入力/出力には、プロパティーを介してアクセスできます。

import openvino.runtime.opset12 as ops

core = ov.Core()

input_a = ops.parameter([8], name="input_a")
res = ops.absolute(input_a)
model = ov.Model(res, [input_a])
compiled = core.compile_model(model, "CPU")
model.outputs[0].tensor.set_names({"result_0"})  # Add name for Output

print(model.inputs)
print(model.outputs)

print(compiled.inputs)
print(compiled.outputs)

さまざまなクラスで使用できるヘルパー関数またはプロパティーについては、Python API ドキュメントを参照してください。

テンソルの操作

Python API では、データをテンソルとして渡すことができます。Tensor オブジェクトは、指定された配列のデータコピーを保持します。numpy 配列の dtype は、OpenVINO™ タイプに自動変換されます。

data_float64 = np.ones(shape=(2,8))

tensor = ov.Tensor(data_float64)
assert tensor.element_type == ov.Type.f64

data_int32 = np.ones(shape=(2,8), dtype=np.int32)

tensor = ov.Tensor(data_int32)
assert tensor.element_type == ov.Type.i32

共有メモリーモード

Tensor オブジェクトは numpy 配列とメモリーを共有できます。shared_memory 引数を指定すると、Tensor オブジェクトはデータをコピーしません。代わりに、numpy 配列のメモリーにアクセスできます。

data_to_share = np.ones(shape=(2,8))

shared_tensor = ov.Tensor(data_to_share, shared_memory=True)

# Editing of the numpy array affects Tensor's data
data_to_share[0][2] = 6.0
assert shared_tensor.data[0][2] == 6.0

# Editing of Tensor's data affects the numpy array
shared_tensor.data[0][2] = 0.6
assert data_to_share[0][2] == 0.6

推論の実行

Python API は、推論の同期モードおよび非同期モードへの追加の呼び出しメソッドをサポートしています。

すべての推論メソッドは、ユーザーが Python dict またはリストに収集された一般的な numpy 配列としてデータを渡すことを許可します。

# Passing inputs data in form of a dictionary
infer_request.infer(inputs={0: data})
# Passing inputs data in form of a list
infer_request.infer(inputs=[data])

推論の結果はさまざまな方法で取得できます。

# Get output tensor
results = infer_request.get_output_tensor().data

# Get tensor with CompiledModel's output node
results = infer_request.get_tensor(compiled.outputs[0]).data

# Get all results with special helper property
results = list(infer_request.results.values())

同期モード - 拡張

Python API は、モデルを推論するさまざまな同期呼び出しを提供しますが、これによりアプリケーションの実行がブロックされます。さらに、これらの呼び出しは推論の結果を返します。

# Simple call to InferRequest
results = infer_request.infer(inputs={0: data})
# Extra feature: calling CompiledModel directly
results = compiled_model(inputs={0: data})

推論結果 - OVDict

同期呼び出しは、OVDict と呼ばれる特別なデータ構造を返します。それは “frozen dictionary (凍った辞書)” に例えることができます。オブジェクトの要素にアクセスするにはさまざまな方法があります。

results = compiled_model(inputs={0: data})

# Access via string
_ = results["result_0"]
# Access via index
_ = results[0]
# Access via output port
_ = results[compiled_model.outputs[0]]
# Use iterator over keys
_ = results[next(iter(results))]
# Iterate over values
_ = next(iter(results.values()))

to_dict() メソッドを使用して、OVDict をネイティブ辞書に変換できます。

警告

to_dict() を使用すると、文字列と整数によるアクセスが失われます。さらに、シャローコピーが実行されるため、変更は元のオブジェクトにも影響する可能性があります。

AsyncInferQueue

非同期モードのパイプラインは、AsyncInferQueue と呼ばれるラッパークラスでサポートできます。このクラスは、InferRequest オブジェクト ( “ジョブ” とも呼ばれます) のプールを自動的に生成し、パイプラインのフローを制御する同期メカニズムを提供します。

各ジョブは一意の id によって区別できます。ID の範囲は 0 から AsyncInferQueue コンストラクターで指定されたジョブの数までです。

start_async 関数呼び出しは同期する必要はありません。キューがビジーまたは過負荷の場合は、利用可能なジョブがあれば待機します。すべての AsyncInferQueue コードブロックは、プール内のすべてのジョブの “グローバル” 同期を提供し、それらへのアクセスが安全であることを保証する wait_all 関数で終わる必要があります。

core = ov.Core()

# Simple model that adds two inputs together
input_a = ops.parameter([8])
input_b = ops.parameter([8])
res = ops.add(input_a, input_b)
model = ov.Model(res, [input_a, input_b])
compiled = core.compile_model(model, "CPU")

# Number of InferRequests that AsyncInferQueue holds
jobs = 4
infer_queue = ov.AsyncInferQueue(compiled, jobs)

# Create data
data = [np.array([i] * 8, dtype=np.float32) for i in range(jobs)]

# Run all jobs
for i in range(len(data)):
    infer_queue.start_async({0: data[i], 1: data[i]})
infer_queue.wait_all()

警告

InferRequest オブジェクトは、AsyncInferQueue オブジェクトを反復処理するか、[id] によって取得され、テンソルの取得などの読み取り専用メソッドで動作することが保証されています。単一要求の変更メソッド (start_async、set_callback など) は、親の AsyncInferQueue オブジェクトを無効な状態にします。

要求から結果の取得

wait_all を呼び出した後は、ジョブとデータに安全にアクセスできるようになります。[id] で特定のジョブを取得すると、InferRequest オブジェクトが返され、出力データをシームレスに取得できます。

results = infer_queue[3].get_output_tensor().data

コールバックの設定

AsyncInferQueue のもう 1 つの機能は、コールバックを設定できることです。コールバックが設定されていると、推論を終了するジョブは Python 関数を呼び出します。コールバック関数には 2 つの引数が必要です。1 つはコールバックを呼び出すリクエストで、InferRequest API 提供します。もう 1 つは “userdata” と呼ばれランタイム値を渡すことができます。これらの値は任意の Python タイプにすることができ、コールバック関数内で使用されます。

AsyncInferQueue のコールバックは、すべてのジョブで均一です。実行時に GIL を取得し、関数内でのデータ操作の安全性を保証します。

data_done = [False for _ in range(jobs)]

def f(request, userdata):
    print(f"Done! Result: {request.get_output_tensor().data}")
    data_done[userdata] = True

infer_queue.set_callback(f)

for i in range(len(data)):
    infer_queue.start_async({0: data[i], 1: data[i]}, userdata=i)
infer_queue.wait_all()

assert all(data_done)

u1、u4、および i4 要素タイプの操作

OpenVINO™ は低精度の要素タイプをサポートしており、Python でそれらを処理する方法がいくつかあります。このような要素タイプを使用して入力テンソルを作成するには、バイト・サイズが元の入力サイズと一致する新しい numpy 配列にデータをパックする必要があります。

from openvino.helpers import pack_data

packed_buffer = pack_data(unt8_data, ov.Type.u4)
# Create tensor with shape in element types
t = ov.Tensor(packed_buffer, [100], ov.Type.u4)

テンソルから低精度の値を抽出して numpy 配列に格納するには、次のヘルパーを使用できます。

from openvino.helpers import unpack_data

unpacked_data = unpack_data(t.data, t.element_type, t.shape)
assert np.array_equal(unpacked_data , unt8_data)

GIL のリリース

Python API の一部の関数は、ワーク量の多いコードの実行中に Global Lock Interpreter (GIL) を解放します。これは、Python スレッドを使用してアプリケーションでより多くの並列処理を実現するのに役立ちます。GIL の詳細については、Python API ドキュメントを参照してください。

import openvino as ov
from threading import Thread

input_data = []

# Processing input data will be done in a separate thread
# while compilation of the model and creation of the infer request
# is going to be executed in the main thread.
def prepare_data(input, image):
    shape = list(input.shape)
    resized_img = np.resize(image, shape)
    input_data.append(resized_img)

core = ov.Core()
model = core.read_model(model_path)
# Create thread with prepare_data function as target and start it
thread = Thread(target=prepare_data, args=[model.input(), image])
thread.start()
# The GIL will be released in compile_model.
# It allows a thread above to start the job,
# while main thread is running in the background.
compiled = core.compile_model(model, "CPU")
# After returning from compile_model, the main thread acquires the GIL
# and starts create_infer_request which releases it once again.
request = compiled.create_infer_request()
# Join the thread to make sure the input_data is ready
thread.join()
# running the inference
request.infer(input_data)

GIL が解放されても、関数は引き続き C++ の Python オブジェクトを変更したり操作したりできます。したがって、参照カウントは変化しません。これらのオブジェクトが別のスレッドと共有される場合に備えて、スレッドの安全性に注意を払う必要があります。Python で複数のスレッドが生成された場合にのみコードに影響する可能性があります。

GIL を解放する関数一覧

  • openvino.runtime.AsyncInferQueue.start_async

  • openvino.runtime.AsyncInferQueue.is_ready

  • openvino.runtime.AsyncInferQueue.wait_all

  • openvino.runtime.AsyncInferQueue.get_idle_request_id

  • openvino.runtime.CompiledModel.create_infer_request

  • openvino.runtime.CompiledModel.infer_new_request

  • openvino.runtime.CompiledModel.__call__

  • openvino.runtime.CompiledModel.export

  • openvino.runtime.CompiledModel.get_runtime_model

  • openvino.runtime.Core.compile_model

  • openvino.runtime.Core.read_model

  • openvino.runtime.Core.import_model

  • openvino.runtime.Core.query_model

  • openvino.runtime.Core.get_available_devices

  • openvino.runtime.InferRequest.infer

  • openvino.runtime.InferRequest.start_async

  • openvino.runtime.InferRequest.wait

  • openvino.runtime.InferRequest.wait_for

  • openvino.runtime.InferRequest.get_profiling_info

  • openvino.runtime.InferRequest.query_state

  • openvino.runtime.Model.reshape

  • openvino.preprocess.PrePostProcessor.build