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"}) # 出力名を追加 

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) 

# numpy 配列の編集は Tensor のデータに影響 
data_to_share[0][2] = 6.0 
assert shared_tensor.data[0][2] == 6.0 

# テンソルのデータの編集は numpy 配列に影響 
shared_tensor.data[0][2] = 0.6 
assert data_to_share[0][2] == 0.6

推論の実行#

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

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

# 入力データを辞書形式で渡す 
infer_request.infer(inputs={0: data}) 
# 入力データをリスト形式で渡す 
infer_request.infer(inputs=[data])

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

# 出力テンソルを取得 
results = infer_request.get_output_tensor().data 

# CompiledModel の出力ノードでテンソルを取得 
results = infer_request.get_tensor(compiled.outputs[0]).data 

# 特別なヘルパー・プロパティーを使用してすべての結果を取得 
results = list(infer_request.results.values())

同期モード - 拡張#

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

# InferRequest の単純な呼び出し 
results = infer_request.infer(inputs={0: data}) 
# 拡張機能: CompiledModel を直接呼び出し 
results = compiled_model(inputs={0: data})

推論結果 - OVDict#

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

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

# 文字列によるアクセス 
_ = results["result_0"] 
# インデックスによるアクセス 
_ = results[0] 
# 出力ポート経由でアクセス 
_ = results[compiled_model.outputs[0]] 
# キーにイテレーターを使用 
_ = results[next(iter(results))] 
# 値を反復 
_ = next(iter(results.values()))

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

警告

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

AsyncInferQueue#

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

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

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

core = ov.Core() 

# 2 つの入力を加算するシンプルなモデル 
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") 

# AsyncInferQueue が保持する InferRequest の数 
jobs = 4 
infer_queue = ov.AsyncInferQueue(compiled, jobs) 

# データの作成 
data = [np.array([i] * 8, dtype=np.float32) for i in range(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) 
# 要素タイプの形状を持つテンソルを作成 
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 = [] 

# 入力データの処理は別のスレッドで行われ、 
# モデルのコンパイルと推論要求の作成は 
# メインスレッドで実行されます。 
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) 
# prepare_data 関数をターゲットとしてスレッドを作成し、開始 
thread = Thread(target=prepare_data, args=[model.input(), image]) 
thread.start() 
# GIL は compile_model でリリースされます。
# メインスレッドがバックグラウンドで実行されている間に、 
# 上位のスレッドがジョブを開始できるようになります。 
compiled = core.compile_model(model, "CPU") 
# compile_model から戻った後、メインスレッドは GIL を取得し、 
# create_infer_request を開始して GIL を再度解放します。 
request = compiled.create_infer_request() 
# スレッドに参加して input_data が準備できていることを確認 
thread.join() 
# 推論を実行 
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