Pytorch: [RFC]メモリフォーマット(別名レイアウト、別名NHWC)のサポート

作成日 2019年04月10日  ·  68コメント  ·  ソース: pytorch/pytorch

問題文

CNN演算子は、テンソル次元の正規の順序を利用して、それらに意味的な意味を割り当てます。 今日のPyTorchの2Dの場合、torch.nn.Conv2dへの入力はNCHW順の4dテンソルである必要があります-

パフォーマンス上の理由から、特定の操作によってアクセスされるメモリが連続して配置され、局所性がより有効に活用されるように、ディメンションを異なる方法で並べ替えることが有益な場合がよくあります。 最も一般的なオプションは、寸法を最後に移動することです-NHWC。 1つの次元をブロックに並べて表示するさらに複雑なメモリ形式が存在する可能性があります。

それを利用するライブラリの例は次のとおりです。

  • cudnnは、NHWCのVoltaでより高速なパフォーマンスを発揮します
  • fbgemmとqnnpackはNCHWをサポートしていません。
  • libxsmmはNCHWをサポートしますが、パフォーマンスの低下は50%(IIRC)のようなものです。

課題は、次元の順序自体の変換にコストがかかることです。したがって、複数のCNN操作が連続して実行される場合( conv(relu(conv)))、一度異なるメモリ形式に変換し、操作を実行して並べ替えると便利です。戻る。

したがって、PyTorchにさまざまな次元の順序を認識させ、イーガーモードとJITモードの両方の操作間でさまざまなメモリ形式のテンソルを渡すことができるようにすることが重要です。 さらに、ヒューリスティックまたは検索手法を適用して、メモリ形式の変更がパフォーマンス的に有益かどうか、モデルのどこでそれを行うのが理にかなっているのかを判断する自動JIT最適化パスがあると便利です。

以下を表すことができるAPIの構築に努めています。

  • EagerとJITのPyTorchに存在する、異なるメモリ形式(最初は次元順のみ)のテンソル。 ブロックされたレイアウトは優先度は低くなりますが、それでも問題ありません。
  • メモリ形式を照会および変更するためのユーザー公開API
  • コアCNNオペレーションは、異なるメモリ形式の入力テンソルを処理し、対応するより高速な実装にルーティングできます。
  • JITパスのメモリ形式について推測および最適化する機能

用語:上記の問題は、「レイアウト」(mxnet)、「data_format」(tf)、「image_format」(keras)、「order」(caffe2)と呼ばれることがよくあります。 PyTorchでは「memoryformat」または「memory_format」という名前を使用することを提案します。 残念ながら、PyTorchでは「layout」という名前が「strided」と「sparse_coo」の値で使用されているため、名前のオプションは使用できません。

影響を受けるオペレーター

以下の演算子は、少なくともメモリ形式に対応している必要があります。 正しい結果を生成することに加えて、明示的に指定されたユーザーの意図を伝播するために、基盤となるライブラリから最高のパフォーマンスを提供し、出力のメモリ形式

  • 畳み込み
  • さまざまな種類のプーリング
  • バッチノルム、レイヤーノルム、インスタンスノルム(通常、どのようなノルムでも)
  • アップサンプリング/補間
  • 機能のドロップアウト
  • ソフトマックスの程度は低いです-次元はそこで手動で指定できますが、効率的な実装は暗黙のnchwレイアウトにのみ存在します
  • パディング
  • 要素ごとの(単項および二項)演算
  • empty_likeなどのメモリ形式を継承するテンソルのコンストラクタ。

APIと動作の変更

PyTorchでメモリフォーマットの概念を定義します。

  • torch.memory_format.channels_firstような定数。 それらは指定されたタイプを持たず、任意の比較可能なオブジェクトにすることができます(enumで始まる可能性がありますが、将来的には名前付きテンソルの概念と相互運用する他のオブジェクトになる可能性があります)

    • 別の方法: torch.channels_first直接使用する

  • 値はchannels_firstchannels_last (定数を少なくするため)
  • 1D画像/ 3Dテンソルの場合、値はNCW、NWC、2D画像/ 4Dテンソルの場合-NCHW、NHWC、3D画像/ 5Dテンソルの場合-NCDHW、NDHWCを意味します

Tensorに次のメソッドを追加します。

  • x.is_contiguous(torch.memory_format.channels_first)
  • x.to(memory_format=torch.memory_format.channels_first)

:現時点ではx.get_memory_format()関数はなく、明示的なチェックのみが行われています。これにより、可能な実装の範囲が広がります。 ただし、追加することもできます。

テンソルのセマンティックレイアウトは常に同じままです-NCHW! x.size()常に(n,c,h,w)返します

操作はメモリ形式の動作を保持します。

  • 畳み込み、プーリングなど(上記を参照)は、入力と同じメモリ形式で出力を返し、内部で最適な実装にディスパッチします
  • 単項要素単位の演算は同じメモリ形式を保持し、隣接するテンソルと同じ速度で実行する必要があります
  • バイナリ要素ごとの操作は、メモリ形式を維持する上でいくつかの合理的な保証を提供します-おそらくより広く定義することができますが、最小は次のとおりです。

    • NHWC +スカラー→NHWC

    • NHWC +列ベクトル→NHWC

  • コアCNNopsの逆方向操作は、順方向パスと同じメモリ形式を保持します。 (出力の入力グラデーションは異なるメモリ形式である可能性があるため、明示的に適用する必要がある場合があります)

メモリ形式は、シリアル化/逆シリアル化によって保持されるテンソルのプロパティです(テンソルがパラメータの場合)。

ストライド実装

今日のPyTorchのテンソルには、論理テンソルがメモリ内にどのように配置されるかを指定するストライドの概念がありstridesと同じ長さのベクトルをsizes 。 論理インデックス(i1, i2, .., ik)要素にインデックスを付けるために、内積をストライドで実行し、 offset + i0*stride0 + i1*stride1 + ... * ik * stridekメモリを検索します。 したがって、隣接するテンソルには、サイズの累積積が逆になるストライドがあります。 たとえば、サイズが(n,c,h,w) 4Dテンソルは、ストライド(c*h*w, h*w, w, 1)持っています。

ストライドは、論理的なデフォルトのNCHW順序を維持しながら、さまざまなメモリ形式(次元の並べ替え)を物理的に表すために使用できます。 これは、メモリフォーマット変換の効果的な定義を次のように提供します。

# implementation of x.to(channels_last)
def to_mem_format_nhwc(x):
    return x.permute(0,2,3,1).contiguous().permute(0,3,1,2)

# implementation of x.to(channels_first)
def to_mem_format_nchw(x):
    return x.contiguous()

NHWC形式では、ストライドベクトルは(c*h*w, 1, c*w, c)です。 したがって、メモリバッファでは、NHWCの重みは連続した順序になります。

ストライドはテストに使用できます。

def is_nhwc_contiguous(x):
    return x.permute(0,2,3,1).is_contiguous()

# or alteratively
def is_nhwc_contiguous(x):
    n,c,h,w = x.size() # in any case the sizes remain in NCHW order
    return x.stride() == (c*h*w, 1, c*w, c)

def is_nchw_contiguous(x):
    return x.is_contiguous()


# operator implementations can just check contiguity and carry on directly on data pointer
def my_sample_op(x):
    if x.is_contiguous(nhwc):
        float* p = x.data();
        # Do we need to go to c++ here? 
        # can we have an example in python?
        n,c,h,w = x.size()
        # operate on `p` as it's guaranteed to be (n,h,w,c) array
        y=my_nhwc_op(p)
        # Do we need to convert the layout of y?

    else:
        # Need to convert x to nhwc layout
        x = x.permute(0,2,3,1).contiguous()
        float *p = x.data();
        # Is this needed?
        y = my_nhwc_op(p)
        return y.permute(0,3,1,2).contiguous()

このアプローチの長所

  • 新しいトップレベルのアイデアやAPIパラメーターを追加することなく、既存のPyTorchのストライドの概念を利用します
  • 正規のNCHW順序でテンソルの論理動作を保持します
  • 入力次元の任意の並べ替えに使用できます
  • 既存のシリアル化ルーチンはすでにテンソルのストライドを保持しています
  • 多くの操作を再利用して、さまざまなメモリレイアウトで作業する機能

短所

  • .contiguous()を呼び出すことは、NCHWに切り替えることと同等であり、ユーザーから、またはいずれかのops内で誤って発生する可能性があります。

    • 演算子がメモリ形式を保持していることを確認するには、演算子の明示的な監査が必要です

  • ブロック/タイル形式では機能しません-別のアプローチが必要です

    • PyTorchで一級市民として追加することを検討することは可能ですが、それははるかに大きな変化です

    • 別の方法は、それらを不透明なハンドル、たとえばMKLDNNテンソルとして扱うことです。

  • 基盤となる実装のパフォーマンス特性は、エンドユーザーにはあまりわかりません

最大の潜在的な問題は、ユーザーの意図が不明確なことです。 ユーザーが本当に別のメモリ形式を望んでいたのか、入力テンソルがたまたまこのようにストライドされたのかを区別する方法はありません。 具体的には、既存の操作の動作が変更されます。現在、畳み込みでは、入力が任意にストライドされた場合でもNCHW連続テンソルしか生成できません。新しい世界では、入力がNHWCとして認識されるため、NHWCも返されます。 セマンティクスは変更されませんが、デバッグが難しいパフォーマンスの問題が発生します。 考えられる解決策は、テンソルにユーザー指定のmemory_formatフラグを明示的にタグ付けし、(ストライドに加えて)この注釈のみに従うことです。

上記の問題を解決するための最初の提案は、テンソルで行われた最後のto(memory_format)呼び出しを記録する「ソフト」メモリ形式タグをテンソルに導入することです。 オペレーターは、この注釈を出力に伝搬する必要があります。 アノテーションは「ソフト」であるため、アノテーションの不一致でハードエラーを発生させるのではなく、プロファイリングモードで警告を生成します。

オペレーターの実装

既存の演算子の署名は変更されません。 オペレーターは、オペレーター内でハードコードされたディスパッチを実行して、より高速な実装にルーティングできます。 実装が利用できない場合は、異なるメモリ形式を介したラウンドトリップが可能です。 別の方法は、エラーメッセージを表示することです。

def maxpool(x: Tensor):
    if x.is_contiguous(torch.layout.NHWC):
        return max_pool_impl_nhwc(x)
    return max_pool_impl_default(x.contiguous())

'conv_nhwc'のような個別の演算子を作成するのではなく、 'conv'のような単一の記号を使用してJITIRの演算子を参照することをお勧めします。 その理由は、単純であり、IRを意味表現のレベルに保つことです。

要素ごとの操作

要素ごとのようなコア操作がメモリ形式を保持し、効率的であることを確認する必要があります。

単項演算は、メモリのブロックが「密」であるかどうか、つまり要素がギャップのない領域にまたがっており、各メモリ位置が1回だけ使用されているかどうかを確認することで一般的に処理できます。 簡単なアルゴリズムで検証できます

def is_dense_format(x):
    p = 1
    for s, d in sorted(zip(x.stride(), x.size())):
        if s != p:
            return False
        p *= d
    return True

def my_unary(x):
    if is_dense_format(x):
        return contig_memory_impl(x.data(), x.numel())
    return default_strided_impl(x)

# is_dense_format can be used in implementations of e.g. empty_like too

パフォーマンスツール

パフォーマンスをデバッグするには、プロファイラーに次のサポートを追加する必要があります。

  • プログラムのどこで実際のメモリの並べ替えが発生するかを確認します。つまり、.contiguous()への呼び出しを追跡します。
  • 呼び出された実装の追跡
  • バイナリ操作などでメモリ形式の変更に関する警告を発行します(「ソフト」アノテーションが役立つ場合)

この機能は、オンデマンドプロファイリングツールに組み込むことができます。

Autogradの処理

後方パスは前方と同じメモリ形式で実行されると予想するのは論理的です。 入ってくるグラデーションが任意にストライドされる可能性があるため、常に自動的に発生するとは限りません。 したがって、フォワードパスはメモリ形式を明示的に認識し、それをautogradクロージャに格納し、backwards関数の前にgradテンソルに適用する必要があります。

可能な実装:

def conv_backward(input, weight, grad_output, grad_weight, grad_input):
  if input.is_contiguous(torch.memory_format.channels_last):
    grad_output = grad_output.to(torch.memory_format.channels_last)
    return conv_backward_nhwc(...)
  else:
    grad_output = grad_output.contiguous()
    return conv_backward_nchw(...)

JITでの表現

現在の提案は次のとおりです。

  • 型注釈のメモリ形式のファーストクラスの処理はまだありません。 代わりに、メモリ形式を操作するパスに必要な形状のルックアサイドマップを維持できます
  • 値ごとの形式の注釈を生成する推論パス(shape_inferenceと同様)
  • 最適なパフォーマンスを得るために必要な場所にto(memory_format)呼び出しを挿入する必要がある場所を見つけるメモリ形式変換パス(手動または自動)

強制の目的で、 assert x.is_contiguous(channels_last)ようなステートメントを利用することもできます。

注:特定のデバイスが優先されるメモリ形式の組み合わせを持っているという情報をどこに保存するかという問題があります(たとえば、NHWCのみを実装するfbgemmへのx86ルート上のqconv)。 1つのオプションは、それをop登録レベルに置くことですが、メモリ形式の注釈は、より多くの副次的な情報のように感じます。 まず、JITパスのどこかに、優先メモリ形式と関連するヒューリスティックを示すグローバルマップを維持することから始めます。 乱雑になった場合は、登録ベースのメカニズムに切り替えることができます。

超えて:ブロックされたレイアウト

より複雑なテンソルのパッキングを追加することを決定した場合、実装コストが高く複雑であるため、ファーストクラスのPyTorchテンソルを使用することは妥当ではない可能性があります。 2つの選択肢が可能です。

  • カスタムCタイプのバインディングのような不透明な表現。 これは、パフォーマンスの最適化に関して多様性が高い推論でのパッキングに選択するオプションです。
  • MKLDNNTensorのようなファーストクラスのテンソルタイプで、この新しいタイプにバインドされた操作の一部(すべてではない)があります

さらに別の方法は、コアPyTorchTensorクラスでブロッキング/タイリングのネイティブサポートを実装することです。

名前付きテンソル関係

NamedTensorの既存の提案は、テンソルの型チェックメカニズムとして構造化されています。現時点では、次元名に意味的な意味は割り当てられていません。 したがって、活性化テンソルの意味を推測する唯一の方法は、所定のNCHW形式を使用し続けることです。 NamedTensorと現在の提案を直交させます。

一部の名前(「チャネル」、「幅」など)の意味を厳密に指定したい場合、オペレーターはこの情報を利用して、より高速な実装にルーティングできます。 ただし、入力テンソルは論理的にNHWC(現在のNCHWではない)メモリ形式であるため、セマンティックの変更になります。

先行技術

TensorFlowは、 data_formatパラメーターを介して、オペレーターレベルでNHWCとNCHWの両方をサポートします。 許容値は、4次元入力の場合は( "NHWC"、 "NCHW")、5次元入力の場合は( "NDHWC"、 "NCDHW")、または入力に依存しないchannels_first / channels_lastです。次元。 パラメータの設定を正しく処理するのはユーザーの責任です。つまり、テンソルによって自動的に追跡されることはありません。

Caffe2は、このパラメータが呼び出された呼び出し、 orderではなくdata_formatそれはまだ明示的に個々のオペレータレベルで適用されます。


付録:考慮されるその他のオプション

リトマスの質問:次のコードは何を出力しますか: tensor_in_nhwc_layout.size(1) -チャネル数(デフォルトはPyTorchのNCHWであるため)または高さ(位置1のNHWCレイアウトにあるため)。

この回答に基づいて、いくつかのオプションが可能です。

  • オプションA-ストライド(上記)。 テンソルレイアウトは完全に内部表現です。 実装-それが最も便利にストライドで行われるように。

    • .size(1)は「チャネル」を返しますが、内部メモリのレイアウトは異なります

    • pro:モデルのコードは変更されません。私のモデルは、次元演算を直接実行できます。 実際、パブリックAPIは変更されていません

    • 短所:ストライドの実装では、多くの演算子が.contiguous()を呼び出し、誤ってレイアウトを元に戻す可能性があります

    • 短所:ユーザーの観点から、opリターンの保証が最も重要であるものを理解する。 このIMOは、ストライドのみのアプローチを排除します。これは、操作が返される形式を理解することが非常に困難になり、「ストライドを無視して、実際にはNCHWに隣接するものを返すだけ」と言うAPIがないためです。 これは、上記の制限に追加されます。

  • オプションB-明示的なNHWCテンソル。 ユーザーは、次元の順序が異なるテンソルを明示的に操作しますが、テンソル自体はそれについて何も知りません。 ユーザーが何を期待しているのかを理解するには、オペレーターレベルで注釈が必要です。

    • .size(1)は「高さ」を返します

    • プロ:魔法がなく、非常に予測可能

    • 短所:モデルをあるレイアウトから別のレイアウトに変更すると、.size()および.reshape()へのすべてのアクセスを追跡する必要がある複雑な操作になります(またはAPIで明示的にする必要がありますか?)

  • オプションB'-レイアウトフラグ付きの明示的なNHWCテンソル。 上記と同じですが、テンソルに注釈を付けて、opsが実装で消費するセマンティックレイアウトをマークすることができます。 その場合、オペレーターレベルの注釈は必要ありません。オペレーターは、入力のレイアウトフラグに基づいてディスパッチを実行できます。
  • オプションC-名前付きテンソル。 ( https://docs.google.com/document/d/1ynu3wA2hcjwOtEng04N904gJjEbZWcINXO_ardX6hxc/edit#heading = h.2gbe5xpga3w9)

    • .size(1)は「高さ」を返しますが、このAPIを使用せず、代わりに.size( 'channel')を使用するようにお願いしています。

    • プロ:非常に明確で、ユーザーが何を望んでいるか

    • con:遷移の問題は解決されません。レイアウトを意識して記述されたすべてのコードで、名前付きテンソルを使用するように強制する必要があります。 そうでない場合-上記と同じ問題が適用されます

  • オプションD-レイアウトは不透明なテンソルタイプです。 MKLDNNまたはSparseTensorを扱うのと同じようにNHWCを扱います-異なるDispatchIDを持つ別々のテンソルタイプ。 これはオプションAに似ていますが、デフォルトの動作にはさまざまなトレードオフがあります。実装されていないopsは、NCHWに戻る代わりに失敗します。

    • .size(1)は引き続き「チャネル」を返します

    • プロ:魔法がなく、明示的な個別のディスパッチにより、運用担当者は必要なものを決定できます

    • 長所/短所:必要なすべての演算子を異なるレイアウトに実装する必要があります。一部の操作が欠落している場合、ユーザーはサポートされていないという明示的なエラーを受け取ります。

    • 短所:期待される結果を予測するのが難しいため、ビューなど、多くの操作を禁止する必要があります。

internals mkldnn triaged

最も参考になるコメント

ところで、なぜlayout固執するのではなく、新しいコンセプトを作成する必要があるのでしょうか。 スパース表現には「channels_last」のような明確に定義されたレイアウトの概念があるとは思わないので、 memory_formats * layouts製品を表す必要はありません( layoutsは現在の使用法を指します) )、ただしmemory_format + layoutsのみ

全てのコメント68件

empty_likeは1つの問題があります。 現在定義されているセマンティクスでは、すべてのストライド情報を削除するため、レイアウトを保持してBCになることはできません。

@VitalyFedyuninは、 .contiguous() torch.memory_layoutビットと

1つの質問-サイズが(n, c, h, w) 4Dテンソルx (n, c, h, w)

x = torch.randn(n,c,h,w)
# x.size(): (n, c, h, w)
# x.stride(): (c*h*w, h*w, w, 1)

奇妙な順列があります

y = x.permute(0, 3, 1, 2)
# y.size(): (n, w, c, h)
# y.stride(): (c*h*w, 1, h*w, w)

次に、NHWC形式で連続しているかどうかを確認します。 以下のようにあなたの論理に従う

def is_nhwc_contiguous(x):
    return x.permute(0,2,3,1).is_contiguous()

# or alternatively
def is_nhwc_contiguous(x):
    n,c,h,w = x.size() # in any case the sizes remain in NCHW order
    return x.stride() == (c*h*w, 1, c*w, c)

どちらの場合も、 is_nhwc_contiguous(y)はTrueを返しますか?

これは正しいです。 ただし、コピー、宛先、および同様の操作中に前後に変換されることを避けたいため、ストライドだけを中継することはできません。

ストライドの順序がメモリ形式と同じである場合はどうなりますか? 例として4Dテンソルを使用してみましょう。 テンソルを説明するために、 sizesstrides 、およびstride_indexesます。

サイズ(n、c、h、w)
物理的な順序で歩きます、すなわち

  • フォーマットがnchwの場合、(n、c、h、w)のストライド
  • フォーマットがnhwcの場合、(n、h、w、c)のストライド。

stride_indexesは、ストライドをnchwサイズにマップします。

  • (0、1、2、3)フォーマットがnchwの場合、
  • (0、2、3、1)フォーマットがnhwcの場合。

nchw形式の場合、これは以前と同じです。 nhwcの場合も同様です。

def is_nhwc_contiguous(x):
     n,c,h,w = x.size()
     return x.stride() == (h*w*c, w*c, c, 1)

def is_nchw_contiguous(x):
    n,c,h,w = x.size()
    return x.stride() == (c*h*w, h*w, w, 1)

def is_nchw_format(x):
    return x.stride_index() == (0, 1, 2, 3) 

def is_nhwc_format(x):
    return x.stride_index == (0, 2, 3, 1)

def is_contiguous(x):
    if (is_nchw_format(x)):
        return is_nchw_contiguous(x)
    else if (is_nhwc_format(x)):
        return  is_nhwc_contiguous(x)
    else:
        warning_not_support()

# or, to use stride_index
def is_contiguous(x):
    return x.stride() == (x.size[x.stride_index[1]]*x.size[x.stride_index[2]]*x.size[x.stride_index[3]], x.size[x.stride_index[2]] * x.size[x.stride_index[3]], x.size[x.stride_index[3]], 1)

これは、ブロックされた形式をサポートするように拡張することもできます。 例としてnChw16cを使用します。

sizes: (n, c, h, w)
block_sizes: (n, c/16, h, w, 16)
strides: strides of (n, c/16, h, w, 16)
stride_indexes: (0, 1, 2, 3, 1)  # assume blocked dimension is always in dense (i.e. on the right side of major dimension)

詳細については、後で詳しく説明します。

nchw連続テンソルのみを受け入れるOPの場合、これはここでの作業になります。

あるいは、プロトタイプを少し変更することもできます。

def is_contiguous(format=nchw):
    ...
def contiguous(format=nchw)
    ...

したがって、デフォルトでは、nchwのみが隣接していると想定しています。 このようにして、これらのOPを書き直す必要はなく、自動的にnchwに並べ替えられます。

以下を表すことができるAPIの構築に努めています。

  • EagerとJITのPyTorchに存在する、異なるメモリ形式(最初は次元順のみ)のテンソル。 ブロックされたレイアウトは優先度は低くなりますが、それでも問題ありません。
  • メモリ形式を照会および変更するためのユーザー公開API
  • コアCNNオペレーションは、異なるメモリ形式の入力テンソルを処理し、対応するより高速な実装にルーティングできます。
  • JITパスのメモリ形式について推測および最適化する機能

素晴らしい提案です! それが正しいかどうかを確認するために私の理解を明示できますか(MKL-DNNフォーマット処理の提案を含む):

この提案が「フォーマット」クラスとして実装されたと思います。 クエリと変更のAPIを仮想として提供する限り、MKL-DNNの複雑なフォーマットに適合する継承/拡張を行うことができます。 または、フォーマットを処理するためのフレームワークを提供し、それらの重要な詳細を私たちにオフロードする限り、他の方法。

OPの実装については、各OPに、パフォーマンスを最大化する優先フォーマットと、機能する互換性のあるフォーマットを設定できます。 要素ごとの演算子(または、より一般的に言えば、メモリ制限OP)には、優先順位がないと想定されます。 OPは、「format」オブジェクトを使用して結果テンソルを生成します。このフォーマットオブジェクトは、デフォルトのpytorch期待値と互換性のあるクエリ/変更セマンティクスを保証し、最適化された関数のシリアル(conv2d(ReLU(conv2d))など)と呼ばれる場合に特定のフォーマットを処理できることを保証します。場合)

@uyongw最初の例についてもう少し明確にしたいと思います。 この例を次のように設定します。「NCHWテンソルがあり、それを奇妙な方法で転置しました(したがって、現在はNWCHのように見えます)。NHWCが隣接しているかどうかを知りたいのです。」 しかし、それはそれを見る間違った方法です。 より良い定式化は、「私はNHWCテンソルを持っており、それをNCHWテンソルに置き換えました」です。

別の言い方をすれば、テンソルの物理的寸法に本質的な意味はありません(ストライドを無視する場合)。 歩幅に関してどのようにそれらを参照するかを考えるとき、私たちはそれらに意味を与えるだけです。

テンソルを説明するために、サイズ、ストライド、stride_indexesがあります

stride_indexesは問題を考えるのに便利な方法だと思いますが、「この(逆?)順列をストライドに適用し、それをストライドとして扱うだけなので、ストライドとは厳密に冗長です。 @VitalyFedyuninと私は、ストライド自体から情報を再構築するのは

したがって、デフォルトでは、nchwのみが隣接していると想定しています。

うん、それは私の計画の読み方です。

@CaoZhongZ

この提案が「フォーマット」クラスとして実装されたと思います。 クエリと変更のAPIを仮想として提供する限り、MKL-DNNの複雑なフォーマットに適合する継承/拡張を行うことができます。 または、フォーマットを処理するためのフレームワークを提供し、それらの重要な詳細を私たちにオフロードする限り、他の方法。

私は実際、それが提案の正確な説明ではないと思います。 ここでの提案がサポートするメモリレイアウトのサポートは、ストライドで表現できるレイアウトのみです。 この方法で表現できないもの(ブロックレイアウトなど)はこの方法では機能せず、より重い「レイアウト」メカニズムでサポートする必要があります。

別の言い方をすれば、テンソルの物理的寸法に本質的な意味はありません(ストライドを無視する場合)。 歩幅に関してどのようにそれらを参照するかを考えるとき、私たちはそれらに意味を与えるだけです。

部分的に同意します:-)しかし、この特定の問題についてはそうではありません。 私はすでにnhwcテンソルを持っているとしましょう。 それから私はそれをnwhcに並べ替えます。 さらにnhwcに順列してから、contiguous()を実行したいと思います。 しかし、私はすでにそれをnhwc連続させました。 混乱しませんか?

stride_indexesは問題を考えるのに便利な方法だと思いますが、「この(逆?)順列をストライドに適用し、それを真のストライドとして扱うだけなので、ストライドと厳密に冗長です。)

私見ですが、nhwc(物理)にストライドがある場合、ストライドと重複することはありません。 サイズ(ロジック)を使用した適切なマッピングが必要なためです。 そうでなければ、実際の順序を伝える方法はありません。

ところで、リバースマッピングを使用することにより、より簡単なアプローチがあります。 たとえば、nchwの場合は(0、1、2、3)であり、nhwcの場合は(0、2、3、1)ではなく(0、3、1、2)です。 つまり、stride_index自体も常にNCHWです。 ただし、問題は、nChw16cやOIhw16i16oなどのブロックされた形式に拡張できないことです。

ブロックされた形式では、まったく異なる一連の演算子の実装が必要です。 そのため、これらを「メモリ形式」と混合しないことをお勧めします。これは、定義上、既存のすべての演算子と使いやすく、同じかそれ以上のパフォーマンスで動作するはずです。

部分的に同意します:-)しかし、この特定の問題についてはそうではありません。 私はすでにnhwcテンソルを持っているとしましょう。 それから私はそれをnwhcに並べ替えます。 さらにnhwcに順列してから、contiguous()を実行したいと思います。 しかし、私はすでにそれをnhwc連続させました。 混乱しませんか?

口語的にいくつかの用語を使用していて、精度が必要なため、例を理解するのは困難です。 これが私があなたが言ったことをどのように解釈しているのかです:

  • この提案による「nhwc」テンソル、「物理レイアウトがNHWCであるが、論理レイアウトがNCHWになるようにストライドされたテンソル」。
  • 「(論理レイアウトがNCHWであるテンソル)テンソルを(論理レイアウト)NWHCに並べ替える」とは、物理レイアウトではなく論理レイアウトを並べ替えているため、 y = x.permute(0, 2, 3, 1)を実行することです。 (元の投稿で順列x.permute(0, 3, 1, 2)について言及したため、これはあなたが意図したものではないと思います
  • 次に、(論理レイアウト)NWHCテンソルを(論理レイアウト)NHWCにさらに並べ替えるには、並べ替えz = y.permute(0, 2, 3, 1)を適用します。 これで、論理レイアウトが物理レイアウトと一致するテンソルができました。 これは、 z.contiguous()を尋ねると、真になることを意味します(そして、紛らわしいことに、 z.contiguous(memory_layout=NCHW)も真になります)。しかし、NHWCに隣接するわけではありません。

これはあなたが考えていた例ではないと思います。その場合、「順列」の意味をより正確にする必要があります。

私見ですが、nhwc(物理)にストライドがある場合、ストライドと重複することはありません。 サイズ(ロジック)を使用した適切なマッピングが必要なためです。 そうでなければ、実際の順序を伝える方法はありません。

これが提案の核心です。論理レイアウトとしてNCHWを常に特権として使用ます。 したがって、私が何も知らない4Dテンソルがある場合、その論理レイアウトはNCHWであると

@dzhulgakov

操作はメモリ形式の動作を保持します

物理的なNHWCテンソルが純粋にストライドによって発生する可能性がある場合、メモリ形式タグが存在する場合にのみメモリ形式を保持するようにしない限り、これは技術的にBCを破ります(ただし、これに意味的な意味を持たせたくないようです。提案が現在何を示唆しているのかわかりません。)しかし、これが実際に誰かのコードを実際に壊しているかどうかはわかりません。

物理的なNHWCテンソルが純粋にストライドによって発生する可能性がある場合、メモリ形式タグが存在する場合にのみメモリ形式を保持するようにしない限り、これは技術的にBCを破ります(ただし、これに意味的な意味を持たせたくないようです。提案が現在何を示唆しているのかわかりません。)しかし、これが実際に誰かのコードを実際に壊しているかどうかはわかりません。

メモリフォーマットを「スティッキー」にできると仮定します。 Op over memory形式のテンソルは、メモリ形式のテンソルを生成します。 それはBCの問題を解決します。

ただし、テンソルのメモリ形式が異なる場合は、バイナリ(またはそれ以上のメンバー)操作の動作を定義する必要があります。

@ezyangああ、上記の返信にタイプミスがあることがわかりました。 (申し訳ありませんが、元の例はまだ正しいです。)以下のように言い換えます。

  1. NCHWテンソルがあります(物理的に、隣接しています)。
  2. それから私はそれをNWHCに(論理的に)並べ替えます。
  3. contiguous()呼び出しを続けて、NHWCにさらに並べ替えたいと思います。
  4. NHWCとして(物理的に)使用します。

しかし、ステップ2の後ですでにNHWCが連続しているので、ステップ3をスキップして、ステップ4で直接NHWCとして使用できます。しかし、テンソルの物理的な順序はまったく変わらないため、これは確かに正しくありません。

ブロックされた形式では、まったく異なる一連の演算子の実装が必要です。 そのため、これらを「メモリ形式」と混合しないことをお勧めします。これは、定義上、既存のすべての演算子と使いやすく、同じかそれ以上のパフォーマンスで動作するはずです。

はい、最初のステップとしてNHWCを有効にできます。 しかし、私は実際には、ブロックされたフォーマットが実際にはまったく異なるものだとは思いません。 それは自然に表現することができます(いくつかの良い抽象化で)。 一般的なフォーマットの説明がある場合、他の人は任意のブロッキング/ストライドで新しいフォーマットを登録できます。

さらに、すでにサポートをブロックしている場合は、基礎となるすべてを実行するためにいくつかの隠し構造を作成する必要はありません。これにより、内部に暗黙のワールドが作成され、2つのワールド間のfrom / toが問題になる可能性があります。

とにかく、ブロックされたフォーマットについて考えるのは遠すぎるかもしれません。 しかし、可能であれば、デザインを拡張可能にする方がよいと思います。

しかし、ステップ2の後ですでにNHWCが連続しているので、ステップ3をスキップして、ステップ4で直接NHWCとして使用できます。しかし、テンソルの物理的な順序はまったく変わらないため、これは確かに正しくありません。

OK、あなたの例を理解しました。 実際、ステップ2で停止して、NCHWテンソルであるかのように使用できます。 その場合、WをCなどとして不適切に解釈します。これは、ストライドベースの実装の欠点です( @dzhulgakov 、おそらくこれを提案に追加する必要があります)。 提案には、この場合のいくつかの規定があります。

上記の問題を解決するために、最初の提案は、テンソルで行われた最後のto(memory_format)呼び出しを記録する「ソフト」メモリ形式タグをテンソルに導入することです。 オペレーターは、この注釈を出力に伝搬する必要があります。 アノテーションは「ソフト」であるため、アノテーションの不一致でハードエラーを発生させるのではなく、プロファイリングモードで警告を生成します。

ソフトメモリフォーマットタグを使用すると、並べ替えたNCHWテンソルと、実際には物理的にNHWCであるテンソルを区別できます。 しかし、現在の形式のソフトタグは拘束力がないため、この場合に実際にどれほど役立つかはわかりません。

この問題を解決する別の方法は、名前付きテンソルを使用することです。 名前付きテンソルを使用すると、(論理)次元の名前を使用して、テンソルをNCHW(想定されるデフォルト)またはその他のものとして表示しているかどうかを判断できます。

しかし、私は実際には、ブロックされたフォーマットが実際にはまったく異なるものだとは思いません。 それは自然に表現することができます(いくつかの良い抽象化で)。 一般的なフォーマットの説明がある場合、他の人は任意のブロッキング/ストライドで新しいフォーマットを登録できます。

ここにトピックに関するより多くの解説があります: https

@ezyang返信ありがとうございます。 はい、ソフトフォーマットタグが役立つ場合があります。 懸念されるのは、寸法の順序が任意である可能性があるため、柔軟性が十分でない可能性があることです。 また、それ自体は計算可能ではありません。 名前付きテンソルは、各次元に対して意味的な意味を持っていますが、サポートするためにさらにいくつかの機能が必要になる可能性があります。

個人的には、ストライド順(物理的)からNCHWサイズ順(論理的)へのマップを導入することでこれを解決できると思います。 上で提案したように、NCHWの場合、現在の設計とほぼ同じです。 NHWCの場合、 sizesは引き続きNCHWであり、 stridesは(N、H、W、C)の順序になります。 また、 stride_index =(0、2、3、1)を使用して、ストライドのディメンションインデックスを指定します。

さらに、 stridesstride_indexの組み合わせを使用して、任意のテンソル形式を表すことができます。 これにより、他のユーザーが新しいデータ形式を登録できる柔軟性が得られる場合があります。

@ezyang

操作はメモリ形式の動作を保持します

物理的なNHWCテンソルが純粋にストライドによって発生する可能性がある場合、メモリ形式タグが存在する場合にのみメモリ形式を保持するようにしない限り、これは技術的にBCを破ります(ただし、これに意味的な意味を持たせたくないようです。提案が現在何を示唆しているのかわかりません。)しかし、これが実際に誰かのコードを実際に壊しているかどうかはわかりません。

算術演算としきい値がTensorIteratorに移動されたとき、それは技術的にはBCを破っていました(オペランドのメモリ形式は保持されておらず、TensorIteratorはそれを保持しているため)。 現状は現在、非常に矛盾している-しきい値ジャムは、レイアウト、他のすべての単項演算にはない、torch.whereない両方のオペランドが同じレイアウトを持っていますが、「nchw」またはであるテンソルをデフォルトとなる場合、算術演算は、レイアウトを維持contiguous不一致がある場合、現在の理解では
また、BCではないレイアウトを維持するempty_likeなどについても良い点を述べています。 おそらく、提案のis_contiguousのように、レイアウト引数も必要になります

x.is_contiguous(torch.memory_format.channels_first)

@ezyang @ngimel

empty_likeには1つの問題があります。 現在定義されているセマンティクスでは、すべてのストライド情報を削除するため、レイアウトを保持してBCになることはできません。

また、レイアウトをBCではないままにするempty_likeなどについても良い点を述べています。

物理的な秩序を表現するためにストライドに依存しない場合、 empty_likeは必ずしもBCを破る必要はありません。 テンソルには3種類の次元情報があります。

  • 形状:サイズ
  • 論理順序:ストライドで記録された順序情報(通常、転置または順列をサポートするために使用されます)
  • 物理的な順序:NCHWまたはNHWC(私が提案したようにstride_indexとしてアドレス指定できます)。

現在、物理的な順序は形状/サイズと同じです。 したがって、論理順序を段階的に削除するだけです。 形状と物理的順序を分離していると考えてください。論理順序を削除するだけで、 empty_like形状と物理的順序を保持することもできます。 つまり、 size()stride_index()両方が保持されますが、 stride()はリセットされます。 特に、NHWCテンソルのempty_likeは、同じ形状情報が指定されたNHWC連続テンソルを返します。

@uyongw empty_likeを変更するのが良い考えかどうかはわかりません; 現在、そのセマンティクスはnumpyのempty_like一致しています。

現在の現状は非常に一貫性がありません-しきい値はレイアウトを保持しますが、他のすべての単項演算は保持しません、torch.whereはそうではありません、両方のオペランドが同じレイアウトを持っている場合、算術演算はレイアウトを保持しますが、デフォルトで「nchw」または隣接するテンソルになります不一致があるかどうかの現在の理解では、放送で何が起こるかわかりません。

@ngimel 、はい、これらは現在あまり一貫していません。 メモリ形式を表現する方法を理解することの一部は、演算子を一貫した状態にすることだと思います

リンクした@ zou3519 numpyのempty_likeには、デフォルトで「プロトタイプのレイアウトにできるだけ一致する」というorder引数があります。 これempty_like 、pytorchの

ああ、なるほど、私はそれをあまりにも早く読んでいました。 その場合、empty_likeがnumpyと一致するのもいいでしょうし、ここでもメモリレイアウトを用意するのが良いでしょう(おそらく?)

@ zou3519ええ、私が言おうとしているのは、現在のセマンティクスを維持し( @ezyang@ngimelが述べたように論理的な順序を削除する)、同時にnumpyのデフォルトのような物理的なレイアウトを維持することです。 したがって、NCHWプロトタイプの場合、動作は以前と同じになります。 NHWCプロトタイプの場合、その動作は引き続き互換性があります。つまり、現在の実装を変更しない場合、新しいテンソルはNCHW連続ではなく、NHWC連続になります。

2つの質問:

  • NHWCテンソルがNCHWテンソルに追加された場合はどうなりますか?
  • 次元が物理的にどこにあるかを示す整数値を返すテンソルにt.channel_dim()のようなメソッドを作成することによって、(B)の欠点に対処するのはどうですか? このアプローチは、ネットワークを変更せずにブロック形式などの他の形式を選択できるようにするためにも必要になる場合があります。

(B)の短所を最後の箇条書きで説明すると、(B)の方が私よりも好ましいように思われます。 直感的にわかりやすく、論理的なエラーは簡単に検出できるはずです。 他の隣接するテンソルと同じように見えるため、既存のすべてのopsもテンソルで機能します。 セマンティクスを理解できる(名前付きテンソル提案に類似した)操作も、期待どおりに実行されます。

リンクした@ zou3519 numpyのempty_likeには、デフォルトで「プロトタイプのレイアウトにできるだけ一致する」というorder引数があります。 これempty_like 、pytorchの

このような場合でもフォーマットを維持する予定です(メモリフォーマットのテンソルの場合)

NHWCテンソルがNCHWテンソルに追加された場合はどうなりますか?
メモリ形式のテンソルを使用して操作すると、メモリ形式のテンソルが返されます。 両方のテンソルがメモリ形式の場合、出力形式は最初のテンソルによって決定されます。

私が追加する2つのこと:

このような場合でもフォーマットを維持する予定です(メモリフォーマットのテンソルの場合)

多くの場合、オペレーターはempty_likeを呼び出してから、それらがNCHWに隣接していると想定するため、既存の使用状況を監査する必要があります。 また、サードパーティのコードをどのように処理するかわかりません。 BCを維持したい場合は、numpyとは異なるデフォルトが必要になるようです。

メモリ形式のテンソルを使用して操作すると、メモリ形式のテンソルが返されます。 両方のテンソルがメモリ形式の場合、出力形式は最初のテンソルによって決定されます。

また、出力の形式が本当に気になる場合は、出力テンソルを渡します。

empty_likeに同意しますが、empty_like / zeros_likeなどの結果がnchw-contiguous(物理的に連続している、多くの場合、イメージ操作ではない)と見なされる場合がかなりあります。
out kwargの関数は微分可能ではないため、ほとんどの場合、出力テンソルを渡すことはオプションではありません。

私たちの問題の多くは、期待される出力レイアウトの不一致に起因します。 それらすべてを一度に解決することはできませんが、現在の状態をロックして(少なくともストライドの場合)、1つずつ釘付けにすることはできます。 だからここに提案があります。

Python API

新しいtorch.memory_formatを導入

torch_memory_format.any # default value
torch_memory_format.preserve
torch.memory_format.contiguous # what most of the functions now behave as default
torch.memory_format.nchw # requires 4D tensor, contiguous memory
torch.memory_format.nhwc # requires 4D tensor, restrided/permuted memory

テンソルには明示的なメモリ形式の変換が必要です

x = torch.zeros((10,3,32,32)) # NCHW
x.permute(0,2,3,1).is_contiguous(memory_format=torch.memory_format.nhwc) == False # because memory still layed out as NCHW

特定の形式でそれらに「タグ付け」するには:

y = x.to(memory_format=torch.memory_format.nhwc)
y.is_contiguous(memory_format=torch.memory_format.nhwc) == True # We got new tensor with proper memory layout
y.is_contiguous() == False # Required for back compatibility
y.stride() == (3072, 3, 1, 96)

今、empty_likeなどについて:

z = torch.empty_like(y) 
z.is_contiguous() == True # For BC

それは実際には:

z = torch.empty_like(y, memory_format=torch.memory_format.any ) 

フォーマットを維持したい場合:

z = torch.empty_like(y, memory_format=torch_memory_format.preserve) 
z.is_contiguous() == False 
z.is_contiguous(memory_format=torch.memory_format.nhwc) == True

同様に:

z = torch.empty_like(y, memory_format=memory_format=torch.memory_format.nhwc) 
z.is_contiguous() == False 
z.is_contiguous(memory_format=torch.memory_format.nhwc) == True

つまり、memory_formatのデフォルトの各関数を世界の現在の状態にゆっくりと定義し、それらを分類して、将来どのように変更するかに注意することができます。

テンソルを指定した場合、TensorOptionsは現在無視されます(最良の場合、例外がスローされます。たとえば、渡されたデバイスオプションがoutテンソルデバイスと一致しません)。

メモリフォーマットは軽いはずなので、どんな順列でもそれを失います。

x.zeros((10,3,32,32), memory_format=torch.memory_format.nhwc)
x = x.permute(0,1,3,2).permute(0,1,3,2)
x.is_contiguous(memory_format=torch.memory_format.nhwc) == False (even if strides are similar)

パディングについてはよくわかりません。ここでヘルプをいただければ幸いです。

ただし、x.to(memory_format = torch.memory_format.nhwc) 'tag'テンソルを適切な形式で作成し、selfを返すことができます。

マルチプロセッシング

メモリフォーマット「タグ」を保持します

ブロックメモリフォーマット

上記のAPIは、ディメンション/ストライド/サイズに依存していません。つまり、同じAPIを維持しながら、将来的に機能を拡張できます。

内部API

オペレーターはメモリフォーマットに基づいて分岐することができます

if (self.memory_format(nhwc)) {
 // fast path
} else
{
 // classic implementation
}

TensorOptionsとしてmemory_formatを実行すると、ディスパッチレベルでの分岐について考えることができます(デバイス、レイアウトと同様)

小さなフィードバック@VitalyFedyuninの提案-ここでは4Dテンソルが必要だと思います

torch.memory_format.nchw # requires 4D tensor, contiguous memory
torch.memory_format.nhwc # requires 4D tensor, restrided/permuted memory

制限が厳しすぎて(2Dに加えて1Dと3Dも処理したいため)、元の提案のchannels_first/channels_lastはこの目的に適していました。

同意します。より適切な命名が必要です。 channels_firstは、バッチが最初に実行されることを除いて、ほぼ正しく聞こえます=)

私はあなたの最新の提案が好きです。 .contiguous()の処理は変更されますか? .contiguous(memory_format = <...>)が必要ですか? もしそうなら、そして多くの操作が単に.contiguous()を呼び出す場合でも、メモリを不適切にフォーマットしている可能性があります。 今日の多くの操作では、出力をempty_like()として割り当てますが、これは同じ効果があります。 これらを更新して入力のメモリ形式を検出し、正しい連続したempty_like呼び出しを行う計画はありますか?

現在のところ、ユーザー(およびすべてのライブラリ)は、 .contiguous()が降順でストライドのあるメモリ連続テンソルを返すことを期待しています。

この契約を破ることはできません。 ただし、幸いなことに、memory_formatオプションをサポートするとすぐに、JITは、従来の形式ではなく.contiguous(memory_format=...)を呼び出す方が効率的であるかどうかを理解できるようになります。

@VitalyFedyunin以下のような操作は許可されていないと思いますか?

x.zeros(10,3,32,32)
# x is in nchw (default)
# x.size() is [10,3,32,32]
# x.stride() is [3*32*32, 32*32, 32,1]
x = x.permute(0,2,3,1)
# At this point 
# x.size() is [10,32,32,3], size is not in nchw order
# x.stride() is [3*32*32, 32,1,32*32]

# How can this be supported?
y = x.to(memory_format=torch.memory_format.nhwc)

もう1つのバリアントは次のとおりです。

x.zeros(10,3,32,32)
# `x` is in nchw (default)
# x.size() is [10,3,32,32]
# x.stride() is [3*32*32, 32*32, 32,1]
x = x.permute(0,2,3,1)
x=x.contiguous()
# At this point 
# x.size() is [10,32,32,3], size is not in nchw order
# x.stride() is [32*32*3, 32*3,3,1]

# How can this be supported?
y = x.to(memory_format=torch.memory_format.nhwc)

@ raghuramank100-そもそもなぜユーザーは.permute(0,2,3,1)を呼び出すのでしょうか? この提案のすべてのテンソルのセマンティックサイズは(n、c、h、w)です。これは、size(1)がチャネルを返すことを意味します。 これは、PTの標準ライブラリが今日想定していることであり、この提案でも想定されていることです。 したがって、.permuteを呼び出すことはほとんどありません。

コンテキストマネージャーは、ユーザーがマネージャースコープ内で割り当てられたテンソルのメモリ形式を特定の形式にオーバーライドできるようにするのに役立ちますか?

with torch.memory_format(torch.memory_format.nhwc):
    # a will be allocated with the context managed memory format   
    a = torch.randn(...)

# b will be allocated matching some assumed default format
b = torch.randn(...)

コンテキストマネージャーのアイデアは、memory_formatの制御を緩めるので、好きではありません。

例えば:

with torch.memory_format(torch.channels_last):
  x = torch.randn(10,3,32,32) # this one is NHWC
  y = torch.randn(10,10) @ this one is not

明示的なmemory_formatがそれを明確にするとき:

x = torch.randn(10,3,32,32).to(memory_format=torch.channels_last) # this one is NHWC
y = torch.randn(10,10).to(memory_format=torch.channels_last) # This is errors out as dim == 2

必要に応じて、次のことを可能にする構文を追加できます。

x = torch.randn(10,3,32,32, memory_format=torch.channels_last)

@ raghuramank100順列する必要はありません。

y = x.to(memory_format=torch.channels_last)

xと同じように薄暗い順序を維持しながら、すべてのダーティな作業を行います。

そう:

x = torch.randn(10, 3, 32, 32)
nhwc = x.to(memory_format=torch.channels_last)
self.assertFalse(nhwc.is_contiguous())
self.assertTrue(nhwc.is_contiguous(memory_format=torch.channels_last))
self.assertEqual(nhwc, x)

そして、あなたはこのフォーマットでnhwcに対処し続けることができます

nhwc[N][C][H][W]

@VitalyFedyuninそれは理にかなっています。

ユーザーの観点からは、メソッドの名前付け(このままの場合)は、「to」がTensorを別のデバイスに転送するための推奨される方法であるため、誤解を招くように思われます。

また、C_ORDER配列とF_ORDER配列を変換するためのNumpyのようなものはどうですか?

numpy.asfortranarray()
numpy.ascontiguousarray()

次のようなことは簡単に想像できます。

torch.randn(32, 3, 64, 64).to(device).as_nhwc()

@VitalyFedyunin :別のmemory_formatに変換すると、ユーザーが手動で並べ替える必要がなくなることを理解しています。 ただし、この機能がトーチで使用可能になった場合、ユーザーが上記で概説した順序で関数を呼び出した場合はどうなりますか? 少なくとも、レイアウト変換が失敗したことを示す警告/エラーメッセージが表示されるはずです。

@VitalyFedyunin :別のmemory_formatに変換すると、ユーザーが手動で並べ替える必要がなくなることを理解しています。 ただし、この機能がトーチで使用可能になった場合、ユーザーが上記で概説した順序で関数を呼び出した場合はどうなりますか? 少なくとも、レイアウト変換が失敗したことを示す警告/エラーメッセージが表示されるはずです。

これは、名前付きテンソルを実装する場合にのみ可能になります。 なぜなら今:

x.zeros(10,10,10,10)
x = x.permute(0,2,3,1)

nchwとnhwcのどちらを作成したのか誰にもわかりません。

おそらく私は元の提案を誤解しましたが、記録されたメモリフォーマットタグはこの状況を明確にするはずではありませんか?

@VitalyFedyunin理にかなっていますが、このAPIが安定したときに、これがエンドユーザーに確実に伝達されるようにする必要があります。

@dzhulgakov @ VitalyFedyunin #19975をx[0]考えてみましょう。 xが以前にNHWCテンソルであった場合、これを実行した後、HWCテンソルを取得する必要があります。 Vitalyのパッチがこれを正しく処理しないことはかなり確信しており、ユーザーを非常に混乱させることになると思います。 おそらく、影響を受けるオペレーターは、ストライドをいじくり回しているオペレーターだけです(この場合、オペレーターの数はそれほど多くなく、手動で監査できます)が、それは私たちがすべきことのようです。 どう思いますか?

待ってください、テンソルはまだ次の順序でインデックス付けされたままです:0-dim N; 1次元C; 2番目の薄暗いH; 3次元W。したがって、x [0]は0次元Cのテンソルを返します。 1番目の薄暗いH; 2番目の薄暗いW。xがchannels_firstまたはchannels_lastのメモリレイアウトであるかどうかに関係なく。

それ以外の場合、memory_formatは意味をなさず、テンソルを並べ替えるだけで済みます。

私のポイントは、メモリフォーマットタグが保存されていないということです。 入力テンソルがchannels_lastタグが付けられている場合、新しいテンソルはanyタグが付けられます

cc @ zou3519 、ここでのレイアウト伝播ロジックは、名前付きテンソル作業での名前付き次元伝播の多くを思い出させます。

私はまだこの提案に追いついています。 しかし、 @ ezyangは、次元ごとのフラグ(または名前)を伝播することでレイアウト伝播ロジックを追跡できます。これは、名前の規則を使用して名前付きテンソルを使用するのと同じです。

最初に2つの別々の実装パスとしてそれらを持っていたとしても、メモリタグロジックと名前付きテンソルロジックを正確に並べることができれば素晴らしいでしょう。

フェーズ1

2つのテンソル関数.is_contiguous.contiguous (pythonとc ++ apiの両方)の機能を拡張します。

注: .to(memory_format)関数についていくつかの苦情があり、サポートしないことにしました。

  1. .contiguousは、オプションのキーワードのみの引数- memory_formatサポートするようになりました。これは、 torch.contiguous_formatまたはtorch.channels_lastいずれかになります。

    • torch.contiguous_formatすると、既存の.contiguous()動作が保持されます。

    • x.contiguous(memory_format=torch.channels_last)呼び出すと、同じセマンティックレイアウト(NCHW)を維持しますが、メモリ割り当てパターンが異なる新しいテンソルが返されます。

      x.contiguous(memory_format=torch.channels_last)は、入力テンソルが3d、4d、または5dであることを想定しています。 それ以外の場合は失敗します。

  2. .is_contiguousは、オプションのキーワードのみの引数- memory_formatサポートするようになりました。これは、 torch.contiguous_formatまたはtorch.channels_lastいずれかになります。

    • x.is_contiguous(memory_format=torch.contiguous_format)x.is_contiguous()と同じ機能を保持し、変更されません。

    • x.is_contiguous(memory_format=torch.channels_last)は、A)入力テンソルがメモリ内で連続しており、B)NWHC(または3d、5dの場合は同様)形式でメモリに割り当てられている場合にtrueを返します。

注:フェーズの終わりまでに、1つのx.is_contiguous(memory_format=torch.channels_last)がすべての呼び出しでTensorの状態を計算します。 この機能は後で更新されます。

フェーズ2

特定の操作のためにメモリ形式を保持します。

  1. 単項要素ごとの演算子は、channels_lastメモリ形式を保持します。

    a = torch.randn(N,C,H,W)
    b = a.contiguous(memory_format=torch.channels_last)
    c = b.sin()
    c.is_contiguous(memory_format=torch.channels_last) == True
    
  2. バイナリ要素ごとの演算子( addsubmuldiv )はchannels_lastメモリ形式を保持します。

    a = torch.randn(N,C,H,W)
    b = a.contiguous(memory_format=torch.channels_last)
    c = b * torch.randn(H,W)
    c.is_contiguous(memory_format=torch.channels_last) == True
    
  3. サイズ、ストライド、およびディムを超える操作は、メモリ形式をリセットします。

    a = torch.randn(N,C,H,W)
    b = a.contiguous(memory_format=torch.channels_last)
    c = b.permute(0,2,3,1).permute(0,3,1,2)
    c.is_contiguous(memory_format=torch.channels_last) == False
    

未定のまま

  1. 出力が「channels_last」で判読可能な場合の再整形(および同様の)操作の結果

    import torch
    a = torch.randn(N,C,H,W)
    b = a.contiguous(memory_format=torch.channels_last)
    c = b.reshape(N,C,-1)
    c.is_contiguous(memory_format=torch.channels_last) # ?
    

    注:現在、memory_formatは保持されていません

  2. NHWC + NCHW操作の結果。 NHWCですか?

    注:現在、NHWC + NCHW-> NHWCおよびNCHW + NHWC-> NHWC

cat / splitのような操作はどうですか? 彼らがメモリフォーマットを保存することは有用でしょう。

@ ezyang-インデックス作成に関しては、どこかで停止する必要があると思います。 異なるメモリレイアウトは完全に透過的ではなく、一部の操作ではそれらを無視できるようにする必要があります。 x[0].unsqueeze(0)を含め、 x[0]がタグの消去を許可されるべきだと私は主張します

Raghuが述べたように、cat / splitは、非常に一般的な使用法ですが、可能であればタグを保持する必要があります。 一般的な経験則では、操作によってランクが変更されたり、軸が奇妙に並べ替えられたりしない限り、タグを保持する必要があると思います。 ランクが変更された場合-すべての賭けはオフになります。

場合によってはタグを失うことに同意します。 しかし、私はx[0]については同意しません。 それは私にはNCHWからCHWに行く非常に一般的な方法のように思えます。

テンソルがchannels_last 'タグ'を運ぶ(または持たない)ことがどれほど混乱するかについて何度か話し合った後、bc-breakingchangeを導入してtensorsをchannels_last形式に自動昇格させるリスクを冒すことにしました。

APIにとってそれはどういう意味ですか?

N、1、H、[W、[D]]のようなストライドを持つ3d、4d、5dテンソルは、channels_lastメモリ形式を自動的に取得します。

これを機能させるために、channels_lastテンソルを出力するchannels_lastテンソルの演算子が、隣接するテンソルの演算子と少なくとも同様のパフォーマンスを持つことを保証するために、特別な予防措置を講じます。

最悪のシナリオの場合:
1)ユーザーは出力時に.contiguous()を呼び出すことができます。
2)この動作を変更するのが簡単に近い方法で、自動プロモーションコードを記述します。

このような自動プロモーションの副作用は次のとおりです。

import torch
x = torch.randn(10,16,16,3).permute(0,3,1,2) 
x.is_contiguous(memory_format=torch.channels_last) == True

一方、それはケースを解決することができます(軽い変更の後):

import torch
x = torch.randn(10,3,16,16).contiguous(memory_format=torch.channels_last)
x = x[0].unsqueeze(0)
x.is_contiguous(memory_format=torch.channels_last) == True

@ezyangのリクエストに応じて、スラックコンバージョンから

ナタリア・ギメルシェイン[2:19 PM]
だから私はタグの概念がないだろうと思います。

import torch
#batch = 10, channels = 4, spatial dimensions = 16
x = torch.randn(10,16,16,4).permute(0,3,1,2)
x.is_contiguous(memory_format=torch.channels_last) == True
y = torch.randn(10,16,16,2).permute(0,3,1,2)
x1,x2 = x.chunk(2, dim=1) #chunk along channels dimension, no longer contiguous
x1.is_contiguous(memory_format=torch.channels_last) == False #right? So, if a tensor like this comes into e.g. convolution, what am I supposed to do with it? Did it want to be NHWC? Did it want to be nchw?
z=y+x1 #y is channels_last, x1 is something, what is the z layout?```

Vitaly Fedyunin [8:23 AM]
zはchannels_lastになります

Vitaly Fedyunin [8:25 AM]
提案されたバリアントのいずれかでx1がchannels_lastでない場合(ビューを返さないようにチャンク関数を変更しない限り)、畳み込みはそれをcontiguous(channels_first)形式に変換し、contiguousも返します

Vitaly Fedyunin [9:12 AM]
@ngimelフィードバックをありがとうございます。ビューのような操作が含まれるほとんどの場合をカバーするために、channels_lastのより意味のある定義を出すことができると思います。 ループを維持します。

ナタリア・ギメルシェイン[9:36 AM]
スレッドに返信しました:
それで、それは問題のようです、そうではありませんか? チャネル間のチャンク化は、たとえば開始のようなネットワークでは、比較的一般的なことです。 したがって、テンソルがチャンクチャネルファーストテンソルの場合、畳み込み出力はチャネルファーストになり(直感的な動作であり、ユーザーが望むものである可能性が高い)、テンソルがチャンクチャネルの場合、最後に畳み込み出力が再びチャネルファーストになりますか?

ナタリア・ギメルシェイン[9:39 AM]
スレッドに返信しました:
しかし、非可換加算の振る舞いとyが最初の引数であり、チャネルが最後であるという理由だけでしょ? x1+yの結果はどうなりますか? 二項演算のレイアウト伝播ルールはどこかにありますか?

Vitaly Fedyunin [10:44 AM]
1)はい、代替案で解決しようとしている問題です。 私は今いくつかのテストを行っており、今週(1日か2日で)それを書き留めます。
2)x1 + y-channels_lastも生成する必要があります。そうしないと混乱します。そうです、レイアウト伝播ルールを書き留めておきます。

このことについて直接話し合ったときに

しかし、ここには打ち砕くための詳細がたくさんあるようで、最終的にうまくいくかどうかはわかりません。

したがって、畳み込みのかすみ(および他のレイアウト対応演算子、たとえば、最近調べたアップサンプリングは、入力で.contiguous()を呼び出すことから始まります-それはどういう意味ですか?)が主な理由でした。タグiircを導入してくれました。

ええ、タグのデザインをもう一度開いても大丈夫ですが、
これらのタグをどのように伝播するかという問題を真剣に解決する必要があります。
レイアウトを失った場合でも(チャンクの場合のように)
チャネル上)。 私は「現在のレイアウト」をいくつか作るのがずっと好きです
データに依存させるというよりは、一種のコンテキストマネージャーです。

2019-06-19 12:43:45 -0700のngimelのメッセージからの抜粋:

したがって、畳み込みのかすみ(および他のレイアウト対応演算子、たとえば、最近調べたアップサンプリングは、入力で.contiguous()を呼び出すことから始まります-それはどういう意味ですか?)が主な理由でした。タグiircを導入してくれました。

ところで、なぜlayout固執するのではなく、新しいコンセプトを作成する必要があるのでしょうか。 スパース表現には「channels_last」のような明確に定義されたレイアウトの概念があるとは思わないので、 memory_formats * layouts製品を表す必要はありません( layoutsは現在の使用法を指します) )、ただしmemory_format + layoutsのみ

レイアウトオプションが検討されましたが(付録を確認してください)、コードの重複が多くなり、テンソルを別のmemory_formatにオンザフライで自動変換できないことがわかりました。

結局のところ、memory_formatは、ストライドテンソルをストライドし、ストライドテンソルのプロパティである最適化されたカーネルと出力を簡単に選択する方法であり、完全に異なるクラスではありません。

ある意味で、スパースレイアウトは、ほとんどゼロの配列に最適化されたカーネルを簡単に選択する方法でもあります😄「テンソルを別のmemory_formatにオンザフライで自動変換することを禁止するだけでなく」の部分について詳しく説明してください。

これは単純な質問かもしれませんが、PyTorchがこのAPIを検討するのではなく、運用自体でNHWCを使用するオプションを公開するのではなく、利用可能な場合は基盤となるCuDNNカーネルを直接呼び出すのはなぜですか?

一般的なユースケース(convやプーリングなどのイメージ操作とLMアーキテクチャの混合)の場合、これは簡単な解決策のようです。 開発者として、私が欲しいのはConv2d(..., nhwc=True)です。 これが意味をなさない理由はありますか?

@rewonc同様のアプローチ(カーネルをストライドから派生させる代わりに演算子にオプションを追加する)を検討しましたが、次の理由で適用するのが難しいことがわかりました。

  • このアプローチでは、カーネルがNHWCカーネルを適用するために隣接するテンソルの再試行を行う必要があります。
  • 次の演算子は、 nhwc=Trueオプションがない限り、入力を(連続して)再ライドする必要があります。
  • ネットワーク全体でNHWCを使用するには、すべてのオペレーターにnhwc=Trueオプションが必要です。

PS。 CudNN Ex関数について懸念がある場合は、 cudnn_batch_norm_nhwcおよび同様の演算子を公開することを検討しています。

こんにちは@VitalyFedyunin 、名前付きテンソルがPyTorch1.3でサポートされていることを確認しました。 それはNHWC(またはブロックされた)フォーマットサポートに関する懸念を解決(または部分的に解決)できますか? 名前付きテンソルに基づいてNHWC状態を進める計画はありますか?

チャンネルの最後のサポートを進めています。今週はこことスラックチャンネルでロードマップを公開します。 ブロックされたフォーマットをすぐに追加することは検討していません(すべての演算子を書き直す必要があるため)。

ありがとう。 それはいいでしょう!

https://github.com/pytorch/pytorch/issues/28619内でのタスクと進捗状況の確認

このページは役に立ちましたか?
0 / 5 - 0 評価