ステートフルな OpenVINO モデルの取得¶
元のフレームワークがステートを操作する専用 API を提供していない場合、結果として得られる OpenVINO IR モデルはデフォルトではステートフルではありません。これは、ステートも、Assign 操作と ReadValue 操作も含まれないことを意味します。このようなモデルをステートフルにすることもできます (利点を参照)。それには 3 つの方法があります。
Optimum-Intel - 選択したモデルに適用できるインテルによる自動化ソリューション (この記事では取り上げません。使用ガイドについては、生成 AI モデルの最適化とデプロイを参照してください)。
MakeStateful 変換 - パラメーターと結果のどのペアを置き換えるかを選択します。
LowLatency2 変換 - LSTM/RNN/GRU 操作または Loop/TensorIterator 操作の非表示入力およびセル状態入力に接続されたパラメーターと結果のペアを検出して置換します。
MakeStateful 変換¶
MakeStateful 変換は、ユーザー定義の Parameter と Results のペアを Assign 操作と ReadValue 操作に置き換えることでモデルの構造を変更します。
厳密な構文のみがサポートされます。以下の例に示すように、変換呼び出しは二重引用符 “MakeStateful[…]” で囲み、テンソル名はスペースなしで一重引用符で囲む必要があります: ‘tensor_name_1’。
ステートの命名規則: ほとんどの場合、ステートの名前はパラメーター/結果テンソル名を連結したものです。テンソル名がない場合は、フレンドリー名が使用されます。
例:
ov::Core core;
auto ov_model = core.read_model("path_to_the_model");
std::map<std::string, std::string> tensor_names = {{"tensor_name_1", "tensor_name_4"},
{"tensor_name_3", "tensor_name_6"}};
ov::pass::Manager manager;
manager.register_pass<ov::pass::MakeStateful>(tensor_names);
manager.run_passes(ov_model);
ov::Core core;
auto ov_model = core.read_model("path_to_the_model");
// Parameter_1, Result_1, Parameter_3, Result_3 are shared_ptr<Parameter/Result> in the ov_model
std::vector<std::pair<std::shared_ptr<ov::opset8::Parameter>, std::shared_ptr<ov::opset8::Result>>> pairs
= {/*Parameter_1, Result_1, Parameter_3, Result_3*/};
ov::pass::Manager manager;
manager.register_pass<ov::pass::MakeStateful>(pairs);
manager.run_passes(ov_model);
--input_model <INPUT_MODEL> --transform "MakeStateful[param_res_names={'tensor_name_1':'tensor_name_4','tensor_name_3':'tensor_name_6'}]"
LowLatency2 変換¶
LowLatency2 変換は、次の例に示すように、Parameter と Results のペアを自動的に検出し、Assign 操作と ReadValue 操作で置き換えることにより、TensorIterator と Loop を含むモデルの構造を変更します。
変換を適用した後、上の図に示すように、ReadValue 操作は他の操作を入力として受け取ることができます。これらの入力は、ReadValue 操作の初期化で使用する初期値を設定する必要があります。ただし、このような初期化は、現在のステート API 実装ではサポートされていません。ユーザーがステート API を介して指定しない限り、入力値は無視され、ReadValue 操作の初期値は 0 に設定されます。
LowLatency2 変換の適用¶
-
ov::Model を取得します。
例:ov::Core core; auto ov_model = core.read_model("path_to_the_model");
-
Reshape 機能を使用して、モデル内の TensorIterator/Loop ノード内の反復数を変更します。
例えば、モデル入力の sequence_lengths 次元が 1 を超える場合、TensorIterator レイヤー層の number_of_iterations も 1 を超えあることを意味します。モデルの入力を再形成して、sequence_dimension を正確に 1 に設定できます。
ov_model->reshape({{"X", ov::Shape({1, 1, 16})}});
アンロール: LowLatency2 変換が、内部に反復が 1 つだけある TensorIterator/Loop ノードを含むモデルに適用される場合、これらのノードはアンロールされます。それ以外は、ノードはそのまま残ります。詳細については上の図を参照してください。
-
LowLatency2 変換を適用します。
ov::pass::Manager manager; manager.register_pass<ov::pass::LowLatency2>(); manager.run_passes(ov_model);
(オプション) Const 初期化子引数を使用します。
デフォルトでは、LowLatency2 変換は、前の入力ノードと同じ形状の定数サブグラフを挿入します。ReadValue ノードの初期値はゼロに設定されます。詳細については、下の図を参照してください。
use_const_initializer
引数をfalse
に設定することで、このサブグラフの挿入を無効にできます。manager.register_pass<ov::pass::LowLatency2>(false);
ステートの命名規則: 元の TensorIterator 操作、本体のパラメーター、および追加のサフィックスである
"variable_"
+ id (ゼロベースのインデックス付け、各 TensorIterator の新しいインデックス付け) のいくつかの名前を連結したものです。これらのルールを使用して、変換を適用した後に挿入された状態の名前を予測できます。
例:// Precondition for ov::Model. // TensorIterator and Parameter are created in body of TensorIterator with names std::string tensor_iterator_name = "TI_name"; std::string body_parameter_name = "body_parameter_name"; std::string idx = "0"; // this is a first variable in the network // The State will be named "TI_name/param_name/variable_0" auto state_name = tensor_iterator_name + "//" + body_parameter_name + "//" + "variable_" + idx; //! [ov:get_ov_model] ov::Core core; auto ov_model = core.read_model("path_to_the_model"); //! [ov:get_ov_model] // reshape input if needed //! [ov:reshape_ov_model] ov_model->reshape({{"X", ov::Shape({1, 1, 16})}}); //! [ov:reshape_ov_model] //! [ov:apply_low_latency_2] ov::pass::Manager manager; manager.register_pass<ov::pass::LowLatency2>(); manager.run_passes(ov_model); //! [ov:apply_low_latency_2] auto hd_specific_model = core.compile_model(ov_model); // Try to find the Variable by name auto infer_request = hd_specific_model.create_infer_request(); auto states = infer_request.query_state(); for (auto& state : states) { auto name = state.get_name(); if (name == state_name) { // some actions } }
-
API を使用。OpenVINO ステート API、ステートフル・モデル推論を参照してください。
TensorIterator/Loop レイヤーの反復数を変更する唯一の方法は、Reshape 機能を使用することです。ただし、一部のモデルは再形成できない場合があります。これは、通常、形状の値がモデル内のどこかの定数にハード・コーディングされているためです。
このような場合は、モデル・オプティマイザーのコマンドライン引数
--input
および--output
を使用して、再形成不可能なレイヤーをトリムします。例えば、上の図にあるパラメーターと問題のある定数は、
--input Reshape_layer_name
コマンドライン・オプションを使用してトリミングできます。次の例に示すように、問題のある定数は OpenVINO を使用して置き換えることもできます。// OpenVINO example. How to replace a Constant with hardcoded values of shapes in the network with another one with the new values. // Assume we know which Constant (const_with_hardcoded_shape) prevents the reshape from being applied. // Then we can find this Constant by name on the network and replace it with a new one with the correct shape. ov::Core core; auto model = core.read_model("path_to_model"); // Creating the new Constant with a correct shape. // For the example shown in the picture above, the new values of the Constant should be 1, 1, 10 instead of 1, 49, 10 auto new_const = std::make_shared<ov::opset8::Constant>( /*type, shape, value_with_correct_shape*/ ); for (const auto& node : model->get_ops()) { // Trying to find the problematic Constant by name. if (node->get_friendly_name() == "name_of_non_reshapable_const") { auto const_with_hardcoded_shape = std::dynamic_pointer_cast<ov::opset8::Constant>(node); // Replacing the problematic Constant with a new one. Do this for all the problematic Constants in the network, then // you can apply the reshape feature. ov::replace_node(const_with_hardcoded_shape, new_const); } }
モデル・オプティマイザーを使用した TensorIterator/ループ操作の取得¶
ONNX および ONNX 形式でサポートされるフレームワーク: LSTM、RNN、GRU の元のレイヤーは GRU/RNN/LSTM シーケンス操作に変換されます。ONNX ループレイヤーは OpenVINO ループ操作に変換されます。
TensorFlow: BlockLSTM は TensorIterator 操作に変換されます。TensorIterator 本体には LSTM Cell 操作が含まれます。Peepholes や InputForget などの変更はサポートされていません。While レイヤーは TensorIterator に変換されます。TensorIterator 本体には、サポートされる操作を含めることができます。ただし、形状推論 (モデル・オプティマイザー変換) 中に反復回数を計算できない動的ケースはサポートされていません。
TensorFlow 2: While レイヤーはループ操作に変換されます。ループ本体には、サポートされている操作を含めることができます。
OpenVINO API を介したモデルの作成¶
ステートフルな OpenVINO IR モデルを取得する主なアプローチは、他のフレームワークから変換することです。ただし、モデルを最初から作成することは可能です。その方法については、OpenVINO モデルのビルドを確認してください。
ここでは、ov::SinkVector
を使用して ov::Model
を作成する例も示します。入力と出力を除くステートを持つモデルは、グラフ変換中に削除されないように、Assign
ノードも Model
を指す必要があります。これは、例のようにコンストラクターを使用することも、add_sinks(const SinkVector& sinks) メソッドを使用することもできます。また、delete_sink() メソッドを使用してグラフからノードを削除した後、ov::Model からシンクを削除することもできます。
// ...
auto input = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::Shape{1, 1});
auto init_const = ov::opset8::Constant::create(ov::element::f32, ov::Shape{1, 1}, {0});
// The ReadValue/Assign operations must be used in pairs in the network.
// For each such a pair, its own variable object must be created.
const std::string variable_name("variable0");
ov::op::util::VariableInfo var_info = {init_const->get_shape(),
init_const->get_element_type(),
variable_name};
auto variable = std::make_shared<ov::op::util::Variable>(var_info);
// Creating ov::Model
auto read = std::make_shared<ov::opset8::ReadValue>(init_const, variable);
auto add = std::make_shared<ov::opset8::Add>(input, read);
auto save = std::make_shared<ov::opset8::Assign>(add, variable);
auto result = std::make_shared<ov::opset8::Result>(add);
auto model = std::make_shared<ov::Model>(ov::ResultVector({result}),
ov::SinkVector({save}),
ov::ParameterVector({input}));