ステートフルな OpenVINO モデルの取得#

元のフレームワークがステートを操作する専用 API を提供していない場合、結果として得られる OpenVINO IR モデルはデフォルトではステートフルではありません。これは、ステートも、Assign 操作と ReadValue 操作も含まれないことを意味します。このようなモデルをステートフルにすることもできます (利点を参照)。それには 3 つの方法があります:

MakeStateful トランスフォーメーション#

MakeStateful 変換は、ユーザー定義の Parameter と Results のペアを Assign 操作と ReadValue 操作に置き換えることでモデルの構造を変更します:

MakeStateful トランスフォーメーションのダイアグラム

厳密な構文のみがサポートされます。以下の例に示すように、トランスフォーメーション呼び出しは二重引用符 “MakeStateful[…]” で囲み、テンソル名はスペースなしで一重引用符で囲む必要があります: ‘tensor_name_1’。

ステートの命名規則: ほとんどの場合、ステートの名前はパラメーター/結果テンソル名を連結したものです。テンソル名がない場合は、フレンドリー名 が使用されます。

例:

MakeStateful トランスフォーメーションの詳細なダイアグラム
 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 は ov_model 内の shared_ptr<Parameter/Result> です 
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 操作で置き換えることにより、TensorIteratorLoop を含むモデルの構造を変更します:

LowLatency トランスフォーメーションのダイアグラム

トランスフォーメーションを適用した後、上の図に示すように、ReadValue 操作は他の操作を入力として受け取ることができます。これらの入力は、ReadValue 操作の初期化で使用する初期値を設定する必要があります。ただし、このような初期化は、現在のステート API 実装ではサポートされていません。ユーザーがステート API で指定しない限り、入力値は無視され、ReadValue 操作の初期値は 0 に設定されます。

LowLatency2 トランスフォーメーションを適用するには、以下の手順に従ってください:

  1. ov::Model を取得します。 例:

     ov::Core core; 
    auto ov_model = core.read_model("path_to_the_model");
  2. 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 ノードを含むモデルに適用される場合、これらのノードはアンロールされます。それ以外は、ノードはそのまま残ります。詳細については上の図を参照してください。

  3. 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 の新しいインデックス付け) のいくつかの名前を連結したものです。これらのルールを使用して、変換を適用した後に挿入された状態の名前を予測できます。次に例を示します:

     // ov::Model の前提条件// TensorIterator と Parameter は、TensorIterator の本体に名前で作成されます 
    std::string tensor_iterator_name = "TI_name"; 
    std::string body_parameter_name = "body_parameter_name"; 
    std::string idx = "0"; // これはネットワークの最初の変数です 
    
    // ステートの名前は "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] 
    // 必要に応じて入力内容を変更 
    
    //! [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); 
    // 変数名で検索します 
    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 
        } 
    }
  4. API を使用。OpenVINO ステート APIステートフル・モデル推論を参照してください。

    低レイテンシー制限を示すダイアグラム

    TensorIterator/Loop レイヤーの反復数を変更する唯一の方法は、Reshape 機能を使用することです。ただし、一部のモデルは再形成できない場合があります。これは、通常、形状の値がモデル内のどこかの定数にハード・コーディングされているためです。

    このような場合は、変換パラメーターを使用して、形状を変更できないレイヤーをトリミングします: --input--output。例えば、OpenVINO モデル変換チュートリアルを確認してください。

    上の図にあるパラメーターと問題のある定数は、--input Reshape_layer_name コマンドライン・オプションを使用してトリミングできます。次の例に示すように、問題のある定数は OpenVINO を使用して置き換えることもできます。

     // OpenVINO の例ネットワーク内の形状のハードコードされた値を持つ定数を、新しい値を持つ別の定数に置き換える方法。// どの定数 (const_with_hardcoded_shape) が再形成の適用を妨げるか判明していると仮定します。// 次に、ネットワーク上でこの定数を名前で検索し、正しい形状の新しい定数に置き換えます。 
    ov::Core core; 
    auto model = core.read_model("path_to_model"); 
    // 正しい形状の新しい定数を作成します。// 上の図の例では、定数の新しい値は 1、49、10 ではなく 1、1、10 になります 
    auto new_const = std::make_shared<ov::opset8::Constant>( /*type, shape, value_with_correct_shape*/ ); 
    for (const auto& node : model->get_ops()) { 
        // 問題のある定数を名前で検索します 
        if (node->get_friendly_name() == "name_of_non_reshapable_const") { 
            auto const_with_hardcoded_shape = std::dynamic_pointer_cast<ov::opset8::Constant>(node); 
            // 問題のある定数を新しい定数に置き換えますネットワーク内のすべての問題のある定数に対してこれを実行すると、 
            // 再形成機能を適用できます。 
            ov::replace_node(const_with_hardcoded_shape, new_const); 
        } 
    }

スクラッチからのステートフル・モデル#

ステートフルな 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}); 

// 通常、ReadValue/Assign 操作はモデル内でペアとして表現されます。// ReadValue 操作は内部メモリーバッファーから情報を読み取り、Assign 操作はこのバッファーにデータを書き込みます。// 各ペアごとに、独自の Variable オブジェクトを作成する必要があります。// 変数はバッファーの名前、形状、およびタイプを定義します。 
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); 

// 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}));

ONNX および ONNX 形式でサポートされるフレームワーク: LSTM、RNN、GRU の元のレイヤーは GRU/RNN/LSTM シーケンス操作に変換されます。ONNX ループ レイヤーは OpenVINO ループ操作に変換されます。

TensorFlow: BlockLSTM は TensorIterator 操作に変換されます。この TensorIterator 本体には LSTM Cell 操作が含まれます。Peepholes や InputForget などの変更はサポートされていません。While レイヤーは TensorIterator に変換されます。TensorIterator 本体には、サポートされている操作を含めることができます。ただし、形状推論中に反復回数を計算できない動的ケースはサポートされていません。

TensorFlow 2: While レイヤーはループ操作に変換されます。ループ本体には、サポートされている操作を含めることができます。