Pytorch: [RFC] Unterstützung des Speicherformats (auch bekannt als Layout oder NHWC)

Erstellt am 10. Apr. 2019  ·  68Kommentare  ·  Quelle: pytorch/pytorch

Problemstellung

CNN-Operatoren verwenden die kanonische Ordnung der Tensordimensionen und weisen ihnen eine semantische Bedeutung zu. Für den 2D-Fall in PyTorch muss heute eine Eingabe in Fackel.nn.Conv2d ein 4D-Tensor in NCHW-Reihenfolge sein -.

Aus Leistungsgründen ist es oft von Vorteil, Dimensionen anders anzuordnen, damit der Speicher, auf den durch bestimmte Operationen zugegriffen wird, zusammenhängend angeordnet ist und die Lokalität besser ausgenutzt wird. Die häufigste Option ist das Verschieben von Dimensionen zum Ende hin - NHWC. Es kann noch komplexere Speicherformate geben, die eine Dimension in Blöcke aufteilen, z.

Beispielbibliotheken, die es verwenden, umfassen:

  • cudnn hat eine schnellere Leistung auf Volta in NHWC
  • fbgemm und qnnpack unterstützen NCHW nicht.
  • libxsmm unterstützt NCHW, aber die Leistungseinbußen liegen bei etwa 50 % (IIRC).

Die Herausforderung besteht darin, dass die Transformation der Dimensionsreihenfolge selbst teuer ist. Wenn also mehrere CNNs-Operationen hintereinander ausgeführt werden (z. B. conv(relu(conv))) ), ist es von Vorteil , einmal in das andere Speicherformat zu transformieren , Operationen auszuführen und sie neu anzuordnen zurück.

Daher ist es wichtig, PyTorch unterschiedliche Dimensionsreihenfolgen bewusst zu machen und Tensoren mit unterschiedlichen Speicherformaten zwischen Operationen sowohl im Eager- als auch im JIT-Modus zu übergeben. Darüber hinaus ist es von Vorteil, über automatische JIT-Optimierungsdurchgänge zu verfügen, die versuchen, Heuristiken oder Suchtechniken anzuwenden, um herauszufinden, ob das Ändern des Speicherformats in Bezug auf die Leistung von Vorteil ist und wo es im Modell sinnvoll ist, dies zu tun.

Wir bemühen uns, eine API zu entwickeln, die Folgendes darstellen kann:

  • Tensor mit unterschiedlichem Speicherformat (am Anfang nur Dimensionsreihenfolge) vorhanden in PyTorch in Eager und JIT. Blockierte Layouts haben eine niedrigere Priorität, sind aber trotzdem schön.
  • Benutzerexponierte APIs zum Abfragen und Ändern des Speicherformats
  • CNN-Kernoperationen können Eingabetensoren mit unterschiedlichem Speicherformat und Routing zu einer entsprechenden schnelleren Implementierung verarbeiten
  • Möglichkeit, Speicherformate in JIT-Pässen abzuleiten und zu optimieren

Terminologie : Das obige Problem wird oft als „layout“ (mxnet), „data_format“ (tf), „image_format“ (keras), „order“ (caffe2) bezeichnet. Wir schlagen vor, in PyTorch den Namen „memory format“ oder „memory_format“ zu verwenden. Der Name „layout“ wird in PyTorch leider mit den Werten 'strrided' vs 'sparse_coo' verwendet, so dass die Namensoption nicht zur Verfügung steht.

Betroffene Betreiber

Folgende Operatoren sollten mindestens das Speicherformat berücksichtigen. Sie müssen nicht nur das richtige Ergebnis liefern, sondern auch die beste Leistung der zugrunde liegenden Bibliotheken liefern UND das Speicherformat der Ausgaben

  • Faltung
  • verschiedene Arten von Pooling
  • Batch-Norm, Layer-Norm, Instanz-Norm (allgemein, welche Normen auch immer)
  • Upsampling/Interpolation
  • Funktionsausfall
  • softmax in geringerem Maße - Dimension kann dort manuell angegeben werden, effiziente Implementierungen sind jedoch nur für implizites nchw-Layout vorhanden
  • Polsterung
  • elementweise (unäre und binäre) Operationen
  • Konstruktoren von Tensoren, die das Speicherformat erben, zB empty_like.

API- und Verhaltensänderungen

Definieren Sie das Konzept des Speicherformats in PyTorch:

  • Konstanten wie torch.memory_format.channels_first . Sie haben keinen angegebenen Typ und können beliebige vergleichbare Objekte sein (beginnen wahrscheinlich mit enum, könnten aber in Zukunft andere Objekte sein, die mit dem Konzept des benannten Tensors interoperieren).

    • Alternative: torch.channels_first direkt verwenden

  • Die Werte sind channels_first und channels_last (um weniger Konstanten zu ermöglichen)
  • Für 1D-Bilder / 3D-Tensoren bedeuten die Werte NCW, NWC, für 2D-Bilder / 4D-Tensoren - NCHW, NHWC, für 3D-Bilder / 5D-Tensoren - NCDHW, NDHWC

Fügen Sie Tensor folgende Methoden hinzu:

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

Hinweis : x.get_memory_format() Funktion, sondern nur explizite Überprüfungen - sie ermöglicht eine breitere Palette möglicher Implementierungen. Vielleicht möchten wir es jedoch hinzufügen.

Das semantische Layout der Tensoren bleibt immer gleich - NCHW! x.size() immer (n,c,h,w)

Operationen bewahren das Speicherformatverhalten:

  • Faltung, Pooling usw. (siehe oben) geben die Ausgabe im gleichen Speicherformat wie die Eingabe zurück und leiten sie intern an die beste Implementierung weiter
  • unäre elementweise Operationen behalten das gleiche Speicherformat bei und müssen so schnell wie bei einem zusammenhängenden Tensor ausgeführt werden
  • binäre elementweise Operationen bieten einige vernünftige Garantien für die Beibehaltung des Speicherformats - können wahrscheinlich breiter definiert werden, aber das Minimum ist:

    • NHWC + Skalar → NHWC

    • NHWC + Spaltenvektor → NHWC

  • Rückwärtsoperationen für Kern-CNN-Operationen bewahren das gleiche Speicherformat wie im Vorwärtspfad. (es muss möglicherweise explizit erzwungen werden, da eingehende Gradienten für die Ausgabe in unterschiedlichen Speicherformaten vorliegen können)

Das Speicherformat ist eine Eigenschaft eines Tensors, die durch Serialisierung/Deserialisierung erhalten bleibt (falls der Tensor ein Parameter ist).

Schrittweise Umsetzung

Tensor in PyTorch hat heute ein Konzept von Schritten, die angeben, wie der logische Tensor im Speicher angeordnet ist . Genauer gesagt hat jeder Tensor einen strides Vektor der gleichen Länge wie sizes . Um Elemente in der logischen Indizierung (i1, i2, .., ik) zu indizieren, macht man das Punktprodukt mit Schritten und sucht den Speicher bei offset + i0*stride0 + i1*stride1 + ... * ik * stridek . Aneinandergrenzende Tensoren haben somit Schritte, die umgekehrte kumulative Größenprodukte sind. Zum Beispiel hat ein 4D-Tensor mit den Größen (n,c,h,w) Schritte (c*h*w, h*w, w, 1) .

Strides können verwendet werden, um verschiedene Speicherformate (die eine Dimensionsneuordnung sind) physisch darzustellen, während die logische Standard-NCHW-Reihenfolge beibehalten wird. Es gibt eine effektive Definition der Speicherformattransformation als:

# 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()

Im NHWC-Format ist der Schrittvektor (c*h*w, 1, c*w, c) . Somit sind im Speicherpuffer die Gewichtungen für NHWC in zusammenhängender Reihenfolge.

Strides können zum Testen verwendet werden:

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()

Vorteile dieses Ansatzes:

  • Nutzt das bestehende PyTorch-Konzept der Schritte, ohne neue Ideen der obersten Ebene oder API-Parameter hinzuzufügen
  • Bewahrt das logische Verhalten des Tensors in der kanonischen NCHW-Reihenfolge
  • Funktioniert für die beliebige Neuordnung von Eingabemaßen
  • Vorhandene Serialisierungsroutinen bewahren bereits Tensorschritte
  • Möglichkeit, viele Operationen wiederzuverwenden, um an unterschiedlichen Speicherlayouts zu arbeiten

Nachteile :

  • Das Aufrufen von .contiguous() entspricht dem Wechsel zu NCHW und kann versehentlich vom Benutzer oder innerhalb einer der Ops erfolgen

    • Es ist eine explizite Prüfung der Operatoren erforderlich, um sicherzustellen, dass sie das Speicherformat beibehalten

  • Funktioniert nicht für blockierte / gekachelte Formate - ein anderer Ansatz ist erforderlich

    • Es ist möglich, sie als Bürger erster Klasse in PyTorch hinzuzufügen, aber es ist eine viel größere Änderung

    • Alternativ können sie als undurchsichtige Griffe behandelt werden, z. B. MKLDNN-Tensoren

  • Leistungsmerkmale der zugrunde liegenden Implementierungen sind für den Endbenutzer weniger offensichtlich

Das größte potenzielle Problem ist eine unklare Benutzerabsicht . Es gibt keine Möglichkeit zu unterscheiden, ob der Benutzer wirklich ein anderes Speicherformat wollte oder ob der Eingabetensor zufällig auf diese Weise schritt. Konkret führt dies zu einer Verhaltensänderung für die bestehenden Operationen - heute kann die Faltung nur NCHW-zusammenhängende Tensoren erzeugen, selbst wenn die Eingabe willkürlich ist, in einer neuen Welt könnte sie die Eingabe als NHWC erkennen und daher auch NHWC zurückgeben. Es ändert die Semantik nicht, führt jedoch zu schwer zu debuggenden Leistungsproblemen. Eine mögliche Lösung könnte darin bestehen, Tensoren explizit mit dem benutzerdefinierten Flag memory_format zu kennzeichnen und nur dieser Anmerkung (zusätzlich zu den Schritten) zu folgen.

Um das obige Problem zu lösen, besteht der erste Vorschlag darin, ein „weiches“ Speicherformat-Tag auf dem Tensor einzuführen, das den letzten to(memory_format) Aufruf auf dem Tensor aufzeichnet. Operatoren müssten diese Anmerkung an die Ausgaben weitergeben. Die Annotation ist „weich“, sodass wir bei nicht übereinstimmenden Annotationen keine Fehler machen, sondern im Profiling-Modus Warnungen ausgeben.

Operatorimplementierungen

Die Signatur der bestehenden Operatoren ändert sich nicht. Operatoren können innerhalb des Operators einen fest codierten Dispatch durchführen, um eine schnellere Implementierung zu ermöglichen. Wenn keine Implementierung verfügbar ist, ist ein Round-Tripping durch verschiedene Speicherformate möglich. Alternative wäre eine Fehlermeldung.

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

Es wird bevorzugt, ein einzelnes Symbol wie 'conv' zu verwenden, um auf die Operatoren in JIT IR zu verweisen, anstatt separate Operatoren wie 'conv_nhwc' zu erstellen. Der Grund dafür ist die Einfachheit und das Halten von IR auf der Ebene der semantischen Repräsentation.

Elementweise Operationen

Wir müssen sicherstellen, dass Kernoperationen wie elementweise das Speicherformat beibehalten und effizient sind.

Unäre Operationen können generisch gehandhabt werden, indem überprüft wird, ob ein Speicherblock „dicht“ ist – dh ob Elemente einen Bereich ohne Lücken überspannen und jede Speicherstelle genau einmal verwendet wird. Es kann mit einem einfachen Algorithmus überprüft werden

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

Leistungswerkzeuge

Für die Debugging-Leistung sollten wir dem Profiler Unterstützung für Folgendes hinzufügen:

  • sehen, wo im Programm tatsächliche Speicherneuordnungen auftreten - dh Aufrufe von .contiguous() verfolgen
  • Verfolgen, welche Implementierung aufgerufen wird
  • Warnungen bei Änderungen des Speicherformats in z. B. Binäroperationen ausgeben (wo "weiche" Anmerkungen nützlich sind)

Diese Funktionalität kann in ein On-Demand-Profiling-Tool integriert werden.

Autograd-Handling

Es ist logisch zu erwarten, dass der Rückwärtsdurchlauf mit dem gleichen Speicherformat wie der Vorwärtsdurchlauf ausgeführt wird. Dies geschieht nicht immer automatisch, da eingehende Gradienten willkürlich schritten sein können. Der Vorwärtspass muss also das Speicherformat explizit erkennen, in Autograd-Abschluss speichern und vor der Rückwärtsfunktion auf den Grad-Tensor anwenden.

Mögliche Umsetzung:

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(...)

Vertretung in JIT

Aktueller Vorschlag ist:

  • Noch kein erstklassiges Handling für das Speicherformat in Typannotationen. Stattdessen können wir eine Lookaside-Map in der erforderlichen Form für Durchläufe beibehalten, die das Speicherformat manipulieren
  • Inferenzdurchlauf (ähnlich shape_inference), der Anmerkungen im Format pro Wert erzeugt
  • Speicherformattransformationsdurchgänge (manuell oder automatisch), die erforderlichenfalls to(memory_format) Aufrufe finden, müssen für eine optimale Leistung eingefügt werden

Zu Durchsetzungszwecken können wir auch Aussagen wie assert x.is_contiguous(channels_last) .

Hinweis: Es stellt sich die Frage, wo Informationen darüber gespeichert werden sollen, dass ein bestimmtes Gerät eine bevorzugte Speicherformatkombination hat (z. B. qconv auf x86-Routen zu fbgemm, das nur NHWC implementiert). Eine Möglichkeit besteht darin, es auf der Ebene der Op-Registrierung zu platzieren, jedoch fühlt sich die Annotation des Speicherformats eher wie eine Nebeninformation an. Wir können damit beginnen, dass wir irgendwo im JIT-Pass eine globale Karte pflegen, die bevorzugte Speicherformate und zugehörige Heuristiken bezeichnet. Wenn es unordentlich wird, können wir auf einen registrierungsbasierten Mechanismus umstellen.

Darüber hinaus: blockierte Layouts

Da wir uns entscheiden, komplexere Packungen von Tensoren hinzuzufügen, ist die Verwendung des erstklassigen PyTorch-Tensors aufgrund der hohen Implementierungskosten und der Komplexität möglicherweise nicht plausibel. Zwei Alternativen sind möglich:

  • Undurchsichtige Darstellungen wie benutzerdefinierte C-Typ-Bindungen. Dies ist eine Option für das Packen in Inferenz, bei der die Diversität in Bezug auf die Perf-Optimierung höher ist
  • Erstklassiger Tensortyp wie MKLDNNTensor mit einigen (aber nicht allen) der Operationen, die an diesen neuen Typ gebunden sind

Eine weitere Alternative besteht darin, die native Unterstützung für Blockieren/Kacheln in der Kernklasse PyTorch Tensor zu implementieren.

Benannte Tensor-Relation

Der bestehende Vorschlag für NamedTensor ist als Typprüfungsmechanismus für Tensoren strukturiert - im Moment weist er Dimensionsnamen keine semantische Bedeutung zu. Die einzige Möglichkeit, auf die Bedeutung des Aktivierungstensors zu schließen, besteht darin, weiterhin das vorgegebene NCHW-Format zu verwenden. Es macht NamedTensor und die aktuellen Vorschläge orthogonal.

Wenn wir bereit sind, die Bedeutung einiger Namen (wie "Kanäle", "Breiten") fest zu spezifizieren, können Operatoren diese Informationen verwenden, um eine schnellere Implementierung zu erreichen. Es wäre jedoch eine semantische Änderung, da die Eingabetensoren logischerweise das Speicherformat NHWC (nicht NCHW wie heute) haben würden.

Stand der Technik

TensorFlow unterstützt sowohl NHWC als auch NCHW auf Bedienerebene über den Parameter data_format ; akzeptable Werte sind („NHWC“, „NCHW“) für 4-d-Eingänge, („NDHWC“, „NCDHW“) für 5-d-Eingänge oder channels_first / channels_last unabhängig vom Eingang Dimensionalität. Es liegt in der Hand des Benutzers, die Einstellung des Parameters korrekt zu handhaben, dh er wird nicht automatisch vom Tensor nachgeführt.

Caffe2 ruft diesen Parameter auf und heißt order statt data_format , aber er wird immer noch explizit auf die Ebene einzelner Operatoren angewendet.


Anhang: Andere in Betracht gezogene Optionen

Lackmusfrage: Was gibt der folgende Code aus: tensor_in_nhwc_layout.size(1) - die Anzahl der Kanäle (weil die Standardeinstellung in PyTorch NCHW ist) oder die Höhe (da dies im NHWC-Layout an Position 1 steht).

Basierend auf dieser Antwort sind mehrere Optionen möglich:

  • Option A – Schritte (oben dargestellt). Das Tensor-Layout ist eine vollständig interne Darstellung. Implementierung-wie es am bequemsten mit Schritten erfolgt.

    • .size(1) gibt mir „Kanäle“ zurück, aber der interne Speicher ist anders ausgelegt

    • pro: ändert den Code des Modells nicht, mein Modell kann immer noch direkt Dimensionsarithmetik durchführen. Tatsächlich ändert sich keine der öffentlichen APIs

    • Nachteile: Bei der Implementierung rufen viele Operatoren .contiguous() auf und können das Layout versehentlich zurücksetzen

    • Nachteile: Aus Sicht des Benutzers ist es von größter Bedeutung, die Garantien der Op-Rendite zu verstehen. Diese IMO eliminiert Ansätze nur für Schritte, da es sehr schwierig wird, das Format zu verstehen, in dem Ihre Operation zurückgegeben wird, und es gibt keine API, die sagt: "Ignoriere meine Schritte, gebe eigentlich nur das NCHW-zusammenhängende Ding zurück." Dies gilt zusätzlich zu den oben genannten Einschränkungen.

  • Option B - Expliziter NHWC-Tensor. Der Benutzer manipuliert explizit einen Tensor, der eine andere Dimensionsreihenfolge hat, aber der Tensor selbst weiß nichts darüber. Wir benötigen einige Anmerkungen auf Betreiberebene, um herauszufinden, was der Benutzer erwartet.

    • .size(1) gibt „Höhe“ zurück

    • Pro: keine Magie und sehr vorhersehbar

    • Nachteile: Das Ändern des Modells von einem Layout in ein anderes wird zu einem komplexen Vorgang, der alle Zugriffe auf .size() und .reshape() verfolgen muss (oder müssen Sie dies in der API explizit machen?)

  • Option B' - Expliziter NHWC-Tensor mit Layout-Flag . Wie oben, aber wir erlauben es, dem Tensor eine Annotation hinzuzufügen, um sein semantisches Layout zu markieren, das die Operationen bei ihrer Implementierung verbrauchen. Es ist dann keine Annotation auf Bedienerebene erforderlich - ein Bediener kann den Versand basierend auf dem Layout-Flag der Eingaben durchführen.
  • Option C – Benannter Tensor . ( https://docs.google.com/document/d/1ynu3wA2hcjwOtEng04N904gJjEbZWcINXO_ardX6hxc/edit#heading =h.2gbe5xpga3w9)

    • .size(1) gibt "height" zurück, aber wir bitten die Leute, diese API NICHT zu verwenden und stattdessen .size('channel') zu verwenden

    • Pro: sehr explizit und was der Benutzer will

    • con: löst das Übergangsproblem nicht, wir müssten den gesamten Code, der mit Layout-Bewusstsein geschrieben wurde, zwingen, benannte Tensoren zu verwenden. Wenn nicht - gelten die gleichen Probleme wie oben

  • Option D-Layout ist ein undurchsichtiger Tensortyp . Behandeln Sie NHWC wie wir MKLDNN oder SparseTensor behandeln - separater Tensortyp mit unterschiedlicher DispatchID. Es ist wie Option A, aber mit anderen Kompromissen beim Standardverhalten - nicht implementierte Operationen würden fehlschlagen, anstatt zu NCHW zurückzukehren.

    • .size(1) gibt immer noch "Kanäle" zurück

    • Pro: keine Magie und explizite, separate Zuteilung ermöglicht es den Einsatzkräften zu entscheiden, was sie wollen

    • Vor- und Nachteile: Alle notwendigen Operatoren müssen in einem anderen Layout implementiert werden, wenn eine Op fehlt, erhält der Benutzer eine explizite Fehlermeldung, dass sie nicht unterstützt wird

    • Nachteile: Wir müssten wahrscheinlich viele Operationen darauf verbieten, z. B. Ansichten, weil die erwarteten Ergebnisse schwer vorherzusagen sind

internals mkldnn triaged

Hilfreichster Kommentar

Übrigens, warum müssen wir ein neues Konzept entwickeln, anstatt nur bei layout bleiben? Ich glaube nicht, dass spärliche Darstellungen ein gut definiertes Konzept eines Layouts wie "channels_last" haben, also müssen wir kein Produkt von memory_formats * layouts ( layouts bezieht sich auf die aktuelle Verwendung ), aber nur memory_format + layouts was bedeutet, dass es in Ordnung sein sollte, dasselbe Argument wie früher zu verwenden? Für mich ist es sowohl kürzer als auch schöner und lässt uns vermeiden, die Unterschriften von Fabriken auf tausend Argumente auszudehnen.

Alle 68 Kommentare

Es gibt ein Problem mit empty_like ; Die derzeit definierte Semantik sieht vor, dass alle Schrittinformationen gelöscht werden, sodass es nicht möglich ist, das Layout beizubehalten und BC zu sein.

@VitalyFedyunin ist angemeldet, um die Bits .contiguous() und torch.memory_layout zu implementieren

Eine Frage - für einen 4D-Tensor x mit den Größen (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)

Wir haben eine seltsame Permutation

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

Jetzt prüfen wir, ob es für das NHWC-Format zusammenhängend ist. Folgen Sie Ihrer Logik wie unten

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)

In beiden Fällen wird is_nhwc_contiguous(y) True zurückgeben?

Das ist richtig. Wir können uns jedoch nicht nur auf die Schritte verlassen, da wir jegliche Konvertierungen hin und her beim Kopieren, in und ähnlichen Operationen vermeiden möchten.

Was ist, wenn Schritte dieselbe Reihenfolge wie das Speicherformat haben? Nehmen wir als Beispiel den 4D-Tensor. Um einen Tensor zu beschreiben, haben wir sizes , strides und stride_indexes :

Größen in (n, c, h, b)
Schritte in physischer Reihenfolge, dh

  • Schritte von (n, c, h, w), wenn das Format nchw . ist
  • Schritte von (n, h, w, c), wenn das Format nhwc ist.

stride_indexes ordnet Schritte der nchw-Größe zu:

  • (0, 1, 2, 3) wenn das Format nchw ist,
  • (0, 2, 3, 1) wenn das Format nhwc ist.

Für das nchw-Format ist dies dasselbe wie zuvor. Bei nhwc wird es ähnlich sein.

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)

Dies kann auch erweitert werden, um das blockierte Format zu unterstützen. Verwenden Sie nChw16c als Beispiel,

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)

Weitere Details können später genauer untersucht werden.

Für OPs, die nur nchw zusammenhängenden Tensor akzeptieren, wird das hier etwas Arbeit sein.

Alternativ können wir auch den Prototypen leicht verändern, sagen wir

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

Daher wird standardmäßig davon ausgegangen, dass nur nchw zusammenhängend ist. Auf diese Weise müssen Sie diese OPs nicht neu schreiben, sie werden automatisch in nchw umgeordnet.

Wir bemühen uns, eine API zu entwickeln, die Folgendes darstellen kann:

  • Tensor mit unterschiedlichem Speicherformat (am Anfang nur Dimensionsreihenfolge) vorhanden in PyTorch in Eager und JIT. Blockierte Layouts haben eine niedrigere Priorität, sind aber trotzdem schön.
  • Benutzerexponierte APIs zum Abfragen und Ändern des Speicherformats
  • CNN-Kernoperationen können Eingabetensoren mit unterschiedlichem Speicherformat und Routing zu einer entsprechenden schnelleren Implementierung verarbeiten
  • Möglichkeit, Speicherformate in JIT-Pässen abzuleiten und zu optimieren

Toller Vorschlag! Darf ich mein Verständnis ausdrücken, um zu sehen, ob es richtig ist (einschließlich Vorschläge für die Handhabung von MKL-DNN-Formaten):

Lassen Sie mich vermuten, dass dieser Vorschlag als "Format"-Klasse implementiert wurde. Solange es das Abfragen und Ändern der API als virtuelles Angebot bereitstellt, könnten wir die Vererbung/Erweiterungen durchführen, die zu komplexen MKL-DNN-Formaten passen. Oder andere Methoden, solange sie einen Rahmen für den Umgang mit Formaten bieten und diese Details an uns auslagern.

In Bezug auf die OPs-Implementierung könnte jedes OP ein bevorzugtes Format haben, das seine Leistung maximiert, und ein kompatibles Format, das funktionsfähig ist. Elementweise Operatoren (oder allgemeiner gesagt, speicherbegrenzte OPs) sollen keine Präferenz haben. OP erzeugt seinen Ergebnistensor mit einem "Format" -Objekt. Dieses Formatobjekt garantiert die Abfrage-/Änderungssemantik, die mit der Standard-Pytorch-Erwartung kompatibel ist, sowie dass es bestimmte Formate verarbeiten kann, wenn es als Serien von optimierten Funktionen aufgerufen wird (wie conv2d (ReLU (conv2d)) Fall)

@uyongw Ich möchte etwas mehr zu Ihrem ersten Beispiel

Anders ausgedrückt, die physikalischen Dimensionen eines Tensors haben keine intrinsische Bedeutung (wenn wir Schritte ignorieren). Wir geben ihnen nur eine Bedeutung, wenn wir uns überlegen, wie wir sie in Bezug auf Schritte beziehen.

Um einen Tensor zu beschreiben, haben wir Größen, Schritte und stride_indexes

Ich denke, stride_indexes ist ein bequemer Weg, um über das Problem nachzudenken, aber es ist bei Schritten absolut überflüssig, weil Sie nur sagen: "Wende diese (umgekehrte?) Permutation auf die Schritte an und behandle das dann als die wahre Schritte.) @VitalyFedyunin und ich haben darüber gesprochen, dass es immer noch eine gute Idee sein könnte, diese Informationen in irgendeiner Weise zwischenzuspeichern, da es

Daher wird standardmäßig davon ausgegangen, dass nur nchw zusammenhängend ist.

Ja, das ist meine Lesart des Plans.

@CaoZhongZ

Lassen Sie mich vermuten, dass dieser Vorschlag als "Format"-Klasse implementiert wurde. Solange es das Abfragen und Ändern der API als virtuelles Angebot bereitstellt, könnten wir die Vererbung/Erweiterungen durchführen, die zu komplexen MKL-DNN-Formaten passen. Oder andere Methoden, solange sie einen Rahmen für den Umgang mit Formaten bieten und diese Details an uns auslagern.

Ich glaube nicht, dass dies eine genaue Beschreibung des Vorschlags ist. Die Speicherlayoutunterstützung, die der Vorschlag hier unterstützt, sind nur Layouts, die durch Schritte ausgedrückt werden können. Alles, was auf diese Weise unaussprechlich ist (zB Blocklayout), funktioniert auf diese Weise nicht und muss von unserem schwereren "Layout"-Mechanismus unterstützt werden.

Anders ausgedrückt, die physikalischen Dimensionen eines Tensors haben keine intrinsische Bedeutung (wenn wir Schritte ignorieren). Wir geben ihnen nur eine Bedeutung, wenn wir uns überlegen, wie wir sie in Bezug auf Schritte beziehen.

Stimme teilweise zu :-) Aber nicht bei diesem speziellen Problem. Sprich ich habe schon einen nhwc Tensor. Dann permutiere ich es in nwhc. Ich möchte weiter zu nhwc permutieren und dann ein contiguous() ausführen. Aber ich habe es nhwc zusammenhängend schon bekommen. Ist es nicht verwirrend?

Ich denke, stride_indexes ist eine bequeme Möglichkeit, über das Problem nachzudenken, aber es ist mit Strides strikt überflüssig, denn alles, was Sie sagen, ist "Wende diese (umgekehrte?) Permutation auf Strides an und behandle das dann als die wahren Schritte.)

IMHO, es wird nicht mit Strides überflüssig sein, wenn Sie Strides in nhwc (physisch) haben. Da braucht man ein richtiges Mapping mit Größen(Logik). Ansonsten gibt es keine Möglichkeit, die tatsächliche Reihenfolge zu bestimmen.

Übrigens, es gibt einen einfacheren Ansatz, indem man Reverse Mapping verwendet. Sagen wir, für nchw ist es (0, 1, 2, 3), für nhwc ist es (0, 3, 1, 2) anstelle von (0, 2, 3, 1). Das heißt, der stride_index selbst ist auch immer NCHW. Aber das Problem ist, dass es nicht auf blockierte Formate wie nChw16c oder OIhw16i16o erweitert werden kann.

Blockierte Formate erfordern eine völlig andere Implementierung von Operatoren; Aus diesem Grund ziehen wir es vor, sie nicht mit dem 'Speicherformat' zu mischen, das per Definition mit allen bestehenden Operatoren kompatibel sein und mit gleicher oder besserer Leistung arbeiten soll.

Stimme teilweise zu :-) Aber nicht bei diesem speziellen Problem. Sprich ich habe schon einen nhwc Tensor. Dann permutiere ich es in nwhc. Ich möchte weiter zu nhwc permutieren und dann ein contiguous() ausführen. Aber ich habe es nhwc zusammenhängend schon bekommen. Ist es nicht verwirrend?

Ihr Beispiel ist schwer zu verstehen, da Sie einige Begriffe umgangssprachlich verwenden und Genauigkeit erforderlich ist. So interpretiere ich das, was Sie gesagt haben:

  • Ein "nhwc"-Tensor ist gemäß diesem Vorschlag "Tensor, dessen physikalisches Layout NHWC ist, aber so geschritten ist, dass das logische Layout NCHW ist."
  • Um "einen (Tensor, dessen logisches Layout NCHW ist) zu (logisches Layout) NWHC zu permutieren", müssen Sie y = x.permute(0, 2, 3, 1) ausführen, da Sie das logische Layout und nicht das physische Layout permutieren. (Ich vermute, das ist nicht das, was Sie gemeint haben, denn in Ihrem ursprünglichen Beitrag haben Sie die Permutation x.permute(0, 3, 1, 2) mentioned erwähnt
  • Um dann einen (logischen Layout) NWHC-Tensor zu (logischem Layout) NHWC weiter zu permutieren, muss man die Permutation z = y.permute(0, 2, 3, 1) anwenden. Jetzt haben Sie also einen Tensor, dessen logisches Layout mit dem physischen Layout übereinstimmt. Das bedeutet, dass, wenn wir z.contiguous() fragen, wir wahr werden (und verwirrenderweise wird z.contiguous(memory_layout=NCHW) auch wahr sein). Aber es wird NICHT NHWC zusammenhängend sein.

Ich glaube nicht, dass dies das Beispiel ist, das Sie sich vorgestellt haben. In diesem Fall müssen Sie genauer sagen, was Sie mit "permutieren" meinen.

IMHO, es wird nicht mit Strides überflüssig sein, wenn Sie Strides in nhwc (physisch) haben. Da braucht man ein richtiges Mapping mit Größen(Logik). Ansonsten gibt es keine Möglichkeit, die tatsächliche Reihenfolge zu bestimmen.

Dies ist der Kern des Vorschlags: wir Privileg NCHW als logisches Layout, immer. Wenn ich also einen 4D-Tensor habe, von dem ich nichts weiß, gehe ich davon aus, dass sein logisches Layout NCHW ist. Das beseitigt die Mehrdeutigkeit. Wenn Sie mit Tensoren umgehen möchten, deren logisches Layout nicht NCHW ist, denke ich, dass Ihnen die API wie angegeben das Leben etwas schwer macht.

@dzhulgakov

Operationen bewahren das Speicherformatverhalten

Wenn physische NHWC-Tensoren nur durch Schritte auftreten können, ist dies technisch gesehen BC-brechend, es sei denn, Sie lassen das Speicherformat nur dann erhalten, wenn das Speicherformat-Tag vorhanden ist (aber es klingt so, als ob dies keine semantische Bedeutung haben soll, also ich Ich bin mir nicht sicher, was der Vorschlag derzeit vorschlägt.) Ich bin mir jedoch nicht sicher, ob dies tatsächlich den Code von jemandem in der Praxis bricht.

Wenn physische NHWC-Tensoren nur durch Schritte auftreten können, ist dies technisch gesehen BC-brechend, es sei denn, Sie lassen das Speicherformat nur dann erhalten, wenn das Speicherformat-Tag vorhanden ist (aber es klingt so, als ob dies keine semantische Bedeutung haben soll, also ich Ich bin mir nicht sicher, was der Vorschlag derzeit vorschlägt.) Ich bin mir jedoch nicht sicher, ob dies tatsächlich den Code von jemandem in der Praxis bricht.

Angenommen, wir können das Speicherformat "klebrig" machen. Op over speicherformatierter Tensor erzeugt speicherformatierten Tensor. Das wird das BC-Problem lösen.

Wir müssen jedoch ein Verhalten von binären (oder mehrgliedrigen) Operationen definieren, wenn Tensoren unterschiedliche Speicherformate haben.

@ezyang Oh, ich habe gerade festgestellt, dass sich in meiner obigen Antwort ein Tippfehler befindet. (Das tut mir leid. Das ursprüngliche Beispiel ist jedoch immer noch korrekt.) Lassen Sie es mich wie folgt neu formulieren:

  1. Ich habe einen NCHW-Tensor (physisch, zusammenhängend).
  2. Dann permutiere ich es zu NWHC (logisch).
  3. Ich möchte es weiter in NHWC umwandeln, wobei ein contiguous()-Aufruf gefolgt wird.
  4. Verwenden Sie es als NHWC (physisch).

Aber ich habe es schon nach Schritt 2 NHWC zusammenhängend bekommen. Dann kann ich Schritt 3 überspringen und direkt in Schritt 4 als NHWC verwenden. Aber das ist sicher nicht richtig, da sich die physikalische Ordnung des Tensors überhaupt nicht ändert.

Blockierte Formate erfordern eine völlig andere Implementierung von Operatoren; Aus diesem Grund ziehen wir es vor, sie nicht mit dem 'Speicherformat' zu mischen, das per Definition mit allen bestehenden Operatoren kompatibel sein und mit gleicher oder besserer Leistung arbeiten soll.

Ja, wir können NHWC als ersten Schritt aktivieren. Ich glaube jedoch nicht, dass das blockierte Format wirklich etwas ganz anderes ist. Es kann natürlich ausgedrückt werden (mit einer guten Abstraktion). Wenn es eine allgemeine Formatbeschreibung gibt, können andere einfach neue Formate mit willkürlichen Blockierungen/Schritten registrieren.

Wenn wir die Unterstützung bereits blockiert haben, machen wir uns nicht die Mühe, versteckte Konstrukte zu erstellen, um alles darunterliegende auszuführen, was eine implizite Welt im Inneren erzeugt und das Von/Bis zwischen den beiden Welten zu einem Problem werden kann.

Wie auch immer, es kann zu weit weg sein, über blockierte Formate nachzudenken. Aber ich würde denken, wenn möglich, besser, das Design erweiterbar zu machen.

Aber ich habe es schon nach Schritt 2 NHWC zusammenhängend bekommen. Dann kann ich Schritt 3 überspringen und direkt in Schritt 4 als NHWC verwenden. Aber das ist sicher nicht richtig, da sich die physikalische Ordnung des Tensors überhaupt nicht ändert.

Okay, jetzt verstehe ich dein Beispiel. Sie können tatsächlich bei Schritt 2 aufhören und es verwenden, als ob es ein NCHW-Tensor wäre; in diesem Fall interpretieren Sie W falsch als C usw. Dies ist definitiv ein Nachteil bei der schrittbasierten Implementierung (

Um das obige Problem zu lösen, besteht der erste Vorschlag darin, ein "weiches" Speicherformat-Tag auf dem Tensor einzuführen, das den letzten to(memory_format)-Aufruf aufzeichnet, der auf dem Tensor ausgeführt wurde. Operatoren müssten diese Anmerkung an die Ausgaben weitergeben. Die Annotation ist „weich“, sodass wir bei nicht übereinstimmenden Annotationen keine Fehler machen, sondern im Profiling-Modus Warnungen ausgeben.

Mit dem Soft-Memory-Format-Tag können Sie einen NCHW-Tensor, den Sie permutiert haben, von einem Tensor unterscheiden, der tatsächlich physisch NHWC ist. Aber das Soft-Tag in seiner jetzigen Form ist unverbindlich, daher bin ich mir nicht sicher, wie nützlich es für diesen Fall tatsächlich wäre.

Eine andere Möglichkeit, das Problem zu lösen, sind benannte Tensoren. Bei benannten Tensoren können wir die Namen der (logischen) Dimensionen verwenden, um herauszufinden, ob wir einen Tensor als NCHW (die angenommene Vorgabe) oder etwas anderes betrachten.

Ich glaube jedoch nicht, dass das blockierte Format wirklich etwas ganz anderes ist. Es kann natürlich ausgedrückt werden (mit einer guten Abstraktion). Wenn es eine allgemeine Formatbeschreibung gibt, können andere einfach neue Formate mit willkürlichen Blockierungen/Schritten registrieren.

Weitere Kommentare zum Thema gibt es hier: https://github.com/pytorch/pytorch/issues/16038#issuecomment -454490374

@ezyang Danke für die Antwort. Ja, ein Soft-Format-Tag kann helfen. Das Problem besteht darin, dass es möglicherweise nicht flexibel genug ist, da die Dimensionsreihenfolge beliebig sein kann. Auch ist es selbst nicht berechenbar. Der benannte Tensor hat für jede Dimension eine semantische Bedeutung, benötigt jedoch möglicherweise einige weitere Funktionen zur Unterstützung, die ich bezweifle.

Persönlich würde ich denken, dass dies durch die Einführung einer Karte von der Schrittreihenfolge (physisch) zur NCHW-Größenreihenfolge (logisch) gelöst werden kann. Wie ich oben vorgeschlagen habe, ist es für NCHW fast dasselbe wie das aktuelle Design; für NHWC ist sizes immer noch NCHW, strides wird in (N, H, W, C) Reihenfolge sein. Und wir verwenden stride_index = (0, 2, 3, 1), um den Dimensionsindex der Schritte anzugeben.

Außerdem kann die Kombination von strides und stride_index verwendet werden, um jedes beliebige Tensorformat darzustellen. Dies kann anderen die Flexibilität geben, neue Datenformate zu registrieren.

@ezyang

Operationen bewahren das Speicherformatverhalten

Wenn physische NHWC-Tensoren nur durch Schritte auftreten können, ist dies technisch gesehen BC-brechend, es sei denn, Sie lassen das Speicherformat nur dann erhalten, wenn das Speicherformat-Tag vorhanden ist (aber es klingt so, als ob dies keine semantische Bedeutung haben soll, also ich Ich bin mir nicht sicher, was der Vorschlag derzeit vorschlägt.) Ich bin mir jedoch nicht sicher, ob dies tatsächlich den Code von jemandem in der Praxis bricht.

Als arithmetische Operationen und Schwellenwerte in TensorIterator verschoben wurden, war das technisch gesehen BC-breaking (da das Speicherformat der Operanden früher nicht beibehalten wurde und TensorIterator es behält). Der Status Quo ist jetzt sehr inkonsistent - Schwellenwert behält das Layout bei, alle anderen unären Operationen nicht, Fackel.where nicht, arithmetische Operationen behalten das Layout bei, wenn beide Operanden das gleiche Layout haben, aber standardmäßig "nchw" oder Tensor ist, der contiguous Nach derzeitigem Verständnis bin ich mir nicht sicher, was bei der Übertragung passiert, wenn es eine Diskrepanz gibt.
Sie machen auch einen guten Hinweis darauf, dass empty_like und dergleichen das Layout beibehalten, das nicht BC ist. Vielleicht wird auch ein Layout-Argument benötigt, wie is_contiguous im Vorschlag

x.is_contiguous(torch.memory_format.channels_first)

@ezyang @ngimel

Es gibt ein Problem mit empty_like; Die derzeit definierte Semantik sieht vor, dass alle Schrittinformationen gelöscht werden, sodass es nicht möglich ist, das Layout beizubehalten und BC zu sein.

Sie machen auch einen guten Hinweis darauf, dass empty_like und dergleichen das Layout beibehalten, das nicht BC ist.

Wenn wir uns nicht auf Schritte verlassen, um physische Ordnung auszudrücken, bricht empty_like BC nicht unbedingt. Es gibt 3 Arten von Dimensionsinformationen im Tensor:

  • Form: Größen
  • logische Reihenfolge: Reihenfolgeninformationen, die in Schritten aufgezeichnet werden (normalerweise verwendet, um Transponieren oder Permutieren zu unterstützen)
  • physische Reihenfolge: NCHW oder NHWC (kann wie von mir vorgeschlagen als stride_index adressiert werden).

Derzeit entspricht die physische Bestellung der Form/Größe. Also lassen wir die logische Reihenfolge einfach fallen. Angenommen, wir entkoppeln Form und physikalische Ordnung, wir können auch einfach die logische Ordnung fallen lassen, aber die Form und die physikalische Ordnung für empty_like beibehalten. Das bedeutet, dass sowohl size() als auch stride_index() erhalten bleiben, aber stride() zurückgesetzt werden. Insbesondere empty_like eines NHWC-Tensors gibt einen zusammenhängenden NHWC-Tensor mit denselben angegebenen Forminformationen zurück.

@uyongw Ich bin mir nicht sicher, ob es eine gute Idee wäre, empty_like zu ändern; im Moment stimmt seine Semantik mit

Der Status quo ist jetzt sehr inkonsistent - Schwellenwert behält das Layout bei, alle anderen unären Operationen nicht, Fackel.where nicht, arithmetische Operationen behalten das Layout bei, wenn beide Operanden das gleiche Layout haben, aber standardmäßig "nchw" oder Tensor, der zusammenhängend ist, Nach derzeitigem Verständnis bin ich mir nicht sicher, was bei der Ausstrahlung passiert, wenn es eine Diskrepanz gibt.

@ngimel , ja, diese sind im

@zou3519 numpys empty_like, das Sie verlinkt haben, hat das Argument order , das standardmäßig "dem Layout des Prototyps so genau wie möglich entspricht". Das ist nicht das, was empty_like in pytorch derzeit tut (es gibt "nchw" zurück - zusammenhängender Tensor, auch wenn der Prototyp nicht zusammenhängend ist)

Oh, ich sehe, das habe ich zu schnell gelesen. In diesem Fall wäre es schön, auch unsere empty_like match numpys zu haben und es wäre (wahrscheinlich?) auch gut für das Speicherlayout hier

@zou3519 Ja, was ich damit sagen beizubehalten (die logische Reihenfolge wie @ngimel erwähnt zu

Zwei Fragen:

  • Was passiert, wenn ein NHWC-Tensor zu einem NCHW-Tensor hinzugefügt wird?
  • Wie wäre es, den Nachteil von (B) zu beheben, indem Methoden wie t.channel_dim() auf einem Tensor erstellt werden, die den ganzzahligen Wert zurückgeben, der angibt, wo sich die Dimension physisch befindet? Dieser Ansatz kann sogar erforderlich sein, damit andere Formate, wie Blockformate, ohne Netzwerkänderungen gewählt werden können.

Wenn wir den Nachteil von (B) mit dem letzten Aufzählungspunkt ansprechen, dann erscheint mir (B) vorzuziehen. Es ist intuitiv klar und logische Fehler sollten leicht zu erkennen sein. Alle vorhandenen Ops können auch mit dem Tensor arbeiten, da er wie jeder andere zusammenhängende Tensor aussieht. Ops, die Semantik verstehen können (analog zum benannten Tensorvorschlag) werden auch wie erwartet funktionieren.

@zou3519 numpys empty_like, das Sie verlinkt haben, hat das Argument order , das standardmäßig "dem Layout des Prototyps so genau wie möglich entspricht". Das ist nicht das, was empty_like in pytorch derzeit tut (es gibt "nchw" zurück - zusammenhängender Tensor, auch wenn der Prototyp nicht zusammenhängend ist)

Wir planen, in solchen Fällen das Format beizubehalten (für speicherformatierte Tensoren)

Was passiert, wenn ein NHWC-Tensor zu einem NCHW-Tensor hinzugefügt wird?
Die Operation mit speicherformatiertem Tensor gibt speicherformatierten Tensor zurück. Wenn beide Tensoren speicherformatiert sind, wird das Ausgabeformat durch den ersten Tensor bestimmt.

Zwei Dinge würde ich hinzufügen:

Wir planen, in solchen Fällen das Format beizubehalten (für speicherformatierte Tensoren)

Wir müssten vorhandene Nutzungen überprüfen, da Operatoren oft empty_like aufrufen und dann davon ausgehen, dass sie NCHW-zusammenhängend sind. Und ich weiß nicht, wie wir mit Code von Drittanbietern umgehen würden. Es scheint, als bräuchten wir einen anderen Standard als numpy, wenn wir BC beibehalten möchten.

Die Operation mit speicherformatiertem Tensor gibt speicherformatierten Tensor zurück. Wenn beide Tensoren speicherformatiert sind, wird das Ausgabeformat durch den ersten Tensor bestimmt.

Ich würde auch hinzufügen, wenn es Ihnen wirklich wichtig ist, in welchem ​​Format Ihre Ausgabe erfolgt - übergeben Sie einen Ausgabetensor.

Stimmen Sie sich auf empty_like zu, es gibt einige Fälle, in denen das Ergebnis von empty_like/zeros_like usw.
Die Übergabe des Ausgabetensors ist in den meisten Fällen keine Option, da Funktionen mit out kwarg nicht differenzierbar sind.

Viele unserer Probleme entstehen durch die Inkonsistenz der erwarteten Ausgabelayouts. Wir können sie nicht alle auf einmal lösen, aber wir können versuchen, den aktuellen Zustand (zumindest für Schritte) zu sperren und sie einzeln festzunageln. Hier also der Vorschlag.

Python-API

Einführung des neuen Torchens.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

Der Tensor erfordert eine explizite Speicherformatkonvertierung

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

Um sie mit einem bestimmten Format zu 'kennzeichnen':

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)

Nun zu empty_like und ähnlichem:

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

Denn es ist tatsächlich:

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

Wenn wir das Format beibehalten möchten:

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

Ähnlich:

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

Das bedeutet, dass wir langsam jede Funktion memory_format defaults auf den aktuellen Zustand der Welt definieren, sie klassifizieren und darauf achten können, wie wir sie in Zukunft ändern.

Wenn Sie Tensor TensorOptions derzeit ignoriert werden angeben aus (im besten Fall werfen sie Ausnahme zum Beispiel übergeben wird , Geräteoption Mismatch mit out Tensor - Gerät).

Das Speicherformat soll leicht sein, sodass es bei jeder Permutation verloren geht.

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)

Bei der Polsterung bin ich mir nicht sicher, werde hier Hilfe zu schätzen wissen.

Wir können jedoch x.to(memory_format=torch.memory_format.nhwc) 'tag' Tensor mit dem richtigen Format machen und self zurückgeben

Multiprocessing

Bewahrt das Speicherformat 'tag'

Speicherformate blockieren

Die obige API basiert nicht auf Abmessungen/Schritten/Größen, was bedeutet, dass wir die Funktionalität in Zukunft erweitern können, wobei die gleiche API beibehalten wird.

Interne APIs

Operatoren könnten basierend auf dem Speicherformat verzweigen

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

Wenn wir memory_format als TensorOptions verwenden, können wir über Verzweigungen auf Dispatch-Ebene nachdenken (ähnlich wie bei Gerät, Layout).

Kleines Feedback zum Vorschlag von

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

ist viel zu restriktiv (weil wir neben 2D auch 1D und 3D behandeln wollen), und channels_first/channels_last aus dem ursprünglichen Vorschlag waren für diesen Zweck entgegenkommender.

Stimmen Sie zu, wir brauchen eine bessere Namensgebung. channels_first klingt fast richtig, außer Batch geht zuerst =)

Dein neuster Vorschlag gefällt mir. Würde sich die Handhabung von .contiguous() ändern? Benötigen Sie .contiguous(memory_format=<...>)? Wenn dies der Fall ist und viele Operationen einfach .contiguous() aufrufen, könnten sie den Speicher immer noch falsch formatieren. Viele Operationen ordnen heute auch Ausgaben als empty_like() zu, was den gleichen Effekt hätte. Wäre der Plan, diese zu aktualisieren, um das Speicherformat der Eingänge zu erkennen und die richtigen zusammenhängenden und leeren_ähnlichen Aufrufe durchzuführen?

Was im Moment unsere Benutzer (und alle Bibliotheken) erwarten, dass .contiguous() den Speicher zusammenhängenden Tensor mit Schritten in absteigender Reihenfolge zurückgibt.

Wir können diesen Vertrag nicht brechen. Die gute Nachricht ist jedoch: Sobald wir die Option memory_format unterstützen, könnte JIT verstehen, wann es effizienter ist, .contiguous(memory_format=...) anstelle des klassischen Formats aufzurufen.

@VitalyFedyunin Gehen wir davon aus, dass Operationen wie unten nicht zulässig sind?

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)

Eine weitere Variante wäre:

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 - warum sollte der Benutzer überhaupt .permute(0,2,3,1) anrufen? Alle Tensoren in diesem Vorschlag haben eine semantische Größe von (n,c,h,w), was bedeutet, dass size(1) Ihnen Kanäle zurückgibt. Davon geht die Standardbibliothek von PT heute und auch in diesem Vorschlag aus. Also würde man .permute wahrscheinlich nie nennen

Kann ein Kontextmanager nützlich sein, um dem Benutzer zu ermöglichen, das Speicherformat von zugewiesenen Tensoren innerhalb des Managerbereichs auf ein bestimmtes Format zu überschreiben?

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(...)

Ich mag die Idee des Kontextmanagers nicht, da er die Kontrolle über memory_format lockert.

Zum Beispiel:

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

Wenn explizites memory_format klar macht:

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

Bei Bedarf können wir Syntax hinzufügen, um Folgendes zu ermöglichen:

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

@raghuramank100 Es besteht keine Notwendigkeit, zu permutieren.

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

Wird alle Drecksarbeiten für Sie erledigen und die Dims-Reihenfolge wie in x beibehalten.

So:

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)

Und Sie können nhwc weiterhin in diesem Format ansprechen

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

@VitalyFedyunin Das macht Sinn.

Aus Anwendersicht erscheint mir die Benennung der Methode (wenn es so bleibt) irreführend, da "to" bereits der empfohlene Weg ist, Tensor auf verschiedene Geräte zu übertragen.

Was ist auch mit etwas wie Numpys zum Konvertieren von C_ORDER- und F_ORDER-Arrays?

numpy.asfortranarray()
numpy.ascontiguousarray()

Man kann sich leicht etwas vorstellen wie:

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

@VitalyFedyunin : Ich verstehe, dass die Konvertierung in ein anderes memory_format die manuelle Permutierung durch die Benutzer überflüssig macht. Was würde jedoch passieren, wenn diese Funktionalität in der Fackel verfügbar ist, wenn Benutzer die Funktionen in der oben beschriebenen Reihenfolge aufrufen würden? Wir sollten mindestens eine Warn-/Fehlermeldung erhalten, die besagt, dass die Layout-Transformation fehlgeschlagen ist.

@VitalyFedyunin : Ich verstehe, dass die Konvertierung in ein anderes memory_format die manuelle Permutierung durch die Benutzer überflüssig macht. Was würde jedoch passieren, wenn diese Funktionalität in der Fackel verfügbar ist, wenn Benutzer die Funktionen in der oben beschriebenen Reihenfolge aufrufen würden? Wir sollten mindestens eine Warn-/Fehlermeldung erhalten, die besagt, dass die Layout-Transformation fehlgeschlagen ist.

Dies wird nur möglich sein, wenn wir benannte Tensoren implementieren. Denn im Moment:

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

Niemand kann mir sagen, ob ich gerade nchw oder nhwc erstellt habe.

Vielleicht habe ich den ursprünglichen Vorschlag falsch verstanden, aber soll das aufgezeichnete Speicherformat-Tag diese Situation nicht eindeutig machen?

@VitalyFedyunin Es ist sinnvoll, wir müssen sicherstellen, dass dies den Endbenutzern mitgeteilt wird, wenn sich diese API stabilisiert.

@dzhulgakov @VitalyFedyunin Nach der Überprüfung von #19975 habe ich einige neue Bedenken bezüglich des aufgezeichneten Speicherformat-Tags im Tensor. Mein grundlegendes Problem ist, wie sollen wir entscheiden, ob Operationen Speicher-Tag beibehalten sollen? Ursprünglich hatte ich gedacht, dass nur "alternatives Layout-bewusste" Operatoren über diese Fähigkeiten verfügen müssen. Aber wenn ich mir Vitalys Patch anschaue, denke ich, dass auch einige Kernoperatoren angepasst werden müssen. Betrachten Sie beispielsweise x[0] ; Wenn x zuvor ein NHWC-Tensor war, sollte ich danach einen HWC-Tensor herausholen. Ich bin mir ziemlich sicher, dass Vitalys Patch dies nicht richtig handhabt, und ich wette, das würde die Benutzer sehr verwirren. Vielleicht sind die einzigen Operatoren, die davon betroffen sind, diejenigen, die mit Schritten herumalbern (in diesem Fall gibt es nicht allzu viele von ihnen und wir können sie manuell überprüfen), aber es scheint eine Sache zu sein, die wir tun sollten. Was denken Sie?

Warten Sie, Tensoren bleiben weiterhin in der folgenden Reihenfolge indiziert: 0-dim N; 1.Dim C; 2. Dim H; 3rd-dim W. Also gibt x[0] einen Tensor mit 0-dim C zurück; 1. Dim H; 2nd-dim W. Unabhängig davon, ob x das Speicherlayout "channels_first" oder "channels_last" war.

Ansonsten macht memory_format einfach keinen Sinn und wir brauchen nur den Tensor zu permutieren.

Mein Punkt ist, dass das Speicherformat-Tag nicht beibehalten wird. Wenn der Eingabetensor mit channels_last markiert wurde, wird der neue Tensor mit any markiert

cc @zou3519 , die Layout-Propagierungslogik hier erinnert mich stark an die

Ich hol diesen Vorschlag noch nach. Aber @ezyang könnten wir die Layout-Verbreitungslogik verfolgen,

Es wäre schön, wenn wir die Speicher-Tag-Logik und die benannte Tensor-Logik genau aneinanderreihen könnten, auch wenn wir sie am Anfang als zwei separate Implementierungspfade haben.

Phase 1

Erweitert die Funktionalität von zwei Tensorfunktionen .is_contiguous und .contiguous (sowohl Python- als auch C++-API).

Hinweis: Wir hatten mehrere Beschwerden über die Funktion .to(memory_format) und haben uns entschieden, sie nicht zu unterstützen.

  1. .contiguous jetzt das optionale Nur-Schlüsselwort-Argument - memory_format , das entweder torch.contiguous_format oder torch.channels_last .

    • Die Verwendung von torch.contiguous_format behält das vorhandene Verhalten von .contiguous() bei.

    • Der Aufruf von x.contiguous(memory_format=torch.channels_last) gibt einen neuen Tensor zurück, der das gleiche semantische Layout (NCHW) beibehält, aber ein anderes Speicherzuweisungsmuster hat.

      x.contiguous(memory_format=torch.channels_last) erwartet, dass der Eingabetensor 3d, 4d oder 5d ist; und scheitert sonst.

  2. .is_contiguous jetzt das optionale Nur-Schlüsselwort-Argument - memory_format , das entweder torch.contiguous_format oder torch.channels_last .

    • x.is_contiguous(memory_format=torch.contiguous_format) behält dieselbe Funktionalität wie x.is_contiguous() und bleibt unverändert.

    • x.is_contiguous(memory_format=torch.channels_last) gibt true zurück, wenn A) der Eingabetensor im Speicher zusammenhängend ist UND B) im Speicher im NWHC-Format (oder ähnlich für 3d, 5d) zugewiesen wurde.

Hinweis: Am Ende der Phase 1 berechnet x.is_contiguous(memory_format=torch.channels_last) den Zustand des Tensors bei jedem Aufruf. Diese Funktionalität wird später aktualisiert.

Phase 2

Speicherformat für bestimmte Vorgänge beibehalten:

  1. Unäre elementweise Operatoren behalten das Speicherformat vonchannels_last bei.

    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. Binäre elementweise Operatoren ( add , sub , mul , div ) bewahren das Speicherformat von channel_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. Alle Operationen über Größen, Schritte und Dims ordnen das Speicherformat zurück.

    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
    

Bleibt unentschlossen

  1. Ergebnis der Umformung (und ähnlicher) Operation, wenn die Ausgabe 'channels_last' lesbar ist

    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) # ?
    

    Hinweis: Derzeit wird memory_format nicht beibehalten

  2. Ergebnis des Betriebs NHWC + NCHW. Ist es NHWC?

    Hinweis: Derzeit NHWC + NCHW -> NHWC und NCHW + NHWC -> NHWC

Was ist mit Operationen wie Cat/Split? Es wird für sie nützlich sein, das Speicherformat beizubehalten.

@ezyang - bezüglich der Indizierung denke ich, dass wir irgendwo aufhören sollten. Unterschiedliche Speicherlayouts sind nicht vollständig transparent und einige Ops sollten sie ignorieren dürfen. Ich würde argumentieren, dass x[0] erlaubt sein sollte, das Tag zu löschen, einschließlich x[0].unsqueeze(0)

Wie Raghu erwähnte, sollte cat/split das Tag jedoch nach Möglichkeit beibehalten, da dies eine recht häufige Verwendung ist. Ich denke, die allgemeine Faustregel sollte sein, dass wir das Tag beibehalten sollten, solange der Betrieb den Rang nicht ändert oder die Achse seltsam neu ordnet. Wenn sich der Rang ändert, sind alle Wetten deaktiviert.

Ich stimme zu, dass wir in einigen Fällen das Etikett verlieren werden. Aber bei x[0] würde ich anderer Meinung sein. Das scheint mir ein sehr üblicher Weg zu sein, um von NCHW zu CHW .

Nach mehreren Gesprächen darüber, wie verwirrend es ist, Tensoren zu haben, die channel_last 'Tag' tragen (oder nicht) haben wir uns entschieden, das Risiko einzugehen, bc-breaking change einzuführen und Tensoren automatisch in das Channels_last-Format zu promoten.

Was bedeutet es für die API:

Alle 3d,4d,5d-Tensoren mit Schritten wie N,1,H,[W,[D]] erhalten automatisch das channel_last memory format.

Damit dies funktioniert, werden wir besondere Vorkehrungen treffen, um zu garantieren, dass Operatoren auf channel_last-Tensoren, die channel_last-Tensoren ausgeben, mindestens eine ähnliche Leistung haben wie Operatoren auf zusammenhängenden Tensoren.

Im schlimmsten Szenario:
1) Benutzer können .contiguous() bei der Ausgabe aufrufen.
2) Wir werden Auto-Promoting-Code so schreiben, dass es fast trivial wäre, dieses Verhalten zu ändern.

Nebenwirkungen einer solchen Auto-Promotion sind:

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

Auf der anderen Seite kann es den Fall lösen (nach leichten Modifikationen):

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

Von Slack-Conversions, gemäß der Anfrage von

Natalia Gimelshein [14:19]
Ich gehe also davon aus, dass es kein Konzept für Tags geben würde.

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]
z wird channel_last

Vitaly Fedyunin [8:25]
Wenn x1 in einer der vorgeschlagenen Varianten nicht channel_last ist (es sei denn, wir ändern die Chunk-Funktion so, dass keine Ansichten zurückgegeben werden), konvertiert es die Faltung in das fortlaufende (channels_first)-Format und gibt auch fortlaufend zurück

Vitaly Fedyunin [9:12 Uhr]
@ngimel vielen Dank für das Feedback. Ich denke, wir können eine aussagekräftigere Definition von ansichtsähnliche Operationen beteiligt sind. Wird dich auf dem Laufenden halten.

Natalia Gimelshein [9:36 Uhr]
auf einen Thread geantwortet:
Es scheint also ein Problem zu sein, oder? Das Chunking über die Dimension der Kanäle hinweg ist eine relativ häufige Sache, zB in inception-ähnlichen Netzwerken. Wenn der Tensor also der erste Tensor mit Chunked-Kanälen ist, wird die Faltungsausgabe Kanäle zuerst sein (was ein intuitives Verhalten ist und höchstwahrscheinlich das, was der Benutzer wünscht), wenn der Tensor der Chunked-Channels-zuletzt ist, dann wird die Faltungsausgabe wieder die Kanäle zuerst sein?

Natalia Gimelshein [9:39]
auf einen Thread geantwortet:
Aber nur aufgrund des nicht-kommutativen Additionsverhaltens und y ist das erste Argument und die Kanäle zuletzt, oder? Was wäre das Ergebnis für x1+y ? Haben wir irgendwo Layout-Verbreitungsregeln für binäre Operationen?

Vitaly Fedyunin [10:44]
1) Ja, es ist ein Problem, das wir mit einem alternativen Vorschlag lösen werden. Ich mache jetzt ein paar Tests und werde es diese Woche aufschreiben (in ein oder zwei Tagen).
2) x1+y - sollte auch channel_last erzeugen, sonst ist es verwirrend, und ja, wir werden Layout-Verbreitungsregeln aufgeschrieben haben.

Ich denke, die Beobachtung, die ich @VitalyFedyunin gemacht

Aber es scheint, als gäbe es hier viele Details, die herausgearbeitet werden müssen, und ich bin mir nicht sicher, ob es am Ende funktioniert.

Die Verschwommenheit der Faltung (und anderer Layout-bewusster Operatoren, z. B. Upsampling, die ich mir kürzlich angesehen habe, beginnt mit dem Aufruf von .contiguous() für die Eingabe - was soll das also bedeuten?) war der Hauptgrund für die Einführung des Tags iirc.

Ja, also bin ich damit einverstanden, das Tag-Design wieder zu öffnen, aber dann wir
müssen die Probleme bei der Verbreitung dieser Tags ernsthaft lösen,
auch wenn Sie das Layout verlieren (wie es beim Chunking der Fall gewesen wäre)
auf Kanälen). Ich mache viel lieber "aktuelles Layout" etwas
eine Art Kontextmanager, als ihn datenabhängig zu machen.

Auszüge aus der Nachricht von ngimel vom 19.06.2019 12:43:45 -0700:

Die Verschwommenheit der Faltung (und anderer Layout-bewusster Operatoren, z. B. Upsampling, die ich mir kürzlich angesehen habe, beginnt mit dem Aufruf von .contiguous() für die Eingabe - was soll das also bedeuten?) war der Hauptgrund für die Einführung des Tags iirc.

Übrigens, warum müssen wir ein neues Konzept entwickeln, anstatt nur bei layout bleiben? Ich glaube nicht, dass spärliche Darstellungen ein gut definiertes Konzept eines Layouts wie "channels_last" haben, also müssen wir kein Produkt von memory_formats * layouts ( layouts bezieht sich auf die aktuelle Verwendung ), aber nur memory_format + layouts was bedeutet, dass es in Ordnung sein sollte, dasselbe Argument wie früher zu verwenden? Für mich ist es sowohl kürzer als auch schöner und lässt uns vermeiden, die Unterschriften von Fabriken auf tausend Argumente auszudehnen.

Layout-Option wurde in Betracht gezogen (siehe Anhang), aber wir haben festgestellt, dass dies zu viel Code-Duplizierung führt und die automatische Konvertierung von Tensoren in ein anderes memory_format on fly nicht zulassen wird

Schließlich ist memory_format ein Weg, um den Tensor zu schreiten und optimierte Kernel und Ausgaben einfach auszuwählen, was eine Eigenschaft des schrittförmigen Tensors ist, keine völlig andere Klasse

In gewisser Weise sind spärliche Layouts auch eine Möglichkeit, optimierte Kernel für Arrays auszuwählen, die meistens null sind.

Dies mag eine naive Frage sein, aber warum zieht PyTorch diese API in Betracht, anstatt nur eine Option zur Verwendung von NHWC in den Operationen selbst bereitzustellen, die den zugrunde liegenden CuDNN-Kernel, sofern verfügbar, direkt aufrufen würde?

Es scheint, als ob dies für einen allgemeinen Anwendungsfall (Mischen von Image-Operationen wie Conv und Pooling mit LM-Architekturen) eine einfache Lösung wäre. Als Entwickler möchte ich nur ein Conv2d(..., nhwc=True) . Gibt es einen Grund, warum das keinen Sinn macht?

@rewonc Wir haben den ähnlichen Ansatz in Betracht gezogen (Optionen zu Operatoren hinzufügen, anstatt den Kernel vom Striding abzuleiten) und fanden es aus folgenden Gründen schwierig, ihn anzuwenden:

  • Dieser Ansatz erfordert, dass der Kernel den zusammenhängenden Tensor neu schreibt, um den NHWC-Kernel anzuwenden.
  • Der nächste Operator muss die Eingabe erneut (auf zusammenhängend) umschreiben, es sei denn, er hat auch die Option nhwc=True .
  • Um NHWC im gesamten Netzwerk zu haben, würde jeder einzelne Betreiber die Option nhwc=True benötigen.

PS. Wenn Sie sich über CudNN Ex Funktionen Sorgen machen, möchten wir cudnn_batch_norm_nhwc und ähnliche Operatoren enthüllen.

Hallo @VitalyFedyunin , wir haben gesehen, dass der benannte Tensor in PyTorch 1.3 unterstützt wird. Kann das die Bedenken bezüglich der NHWC- (oder sogar blockierten) Formatunterstützung lösen (oder teilweise lösen)? Gibt es einen Plan, den NHWC-Zustand basierend auf dem benannten Tensor voranzutreiben?

Wir gehen mit dem letzten Support der Kanäle voran. Ich werde die Roadmap diese Woche hier und in Slack-Kanälen veröffentlichen. Wir erwägen nicht, in absehbarer Zeit blockierte Formate hinzuzufügen (da dazu ALLE Operatoren neu geschrieben werden müssen).

Vielen Dank. Das wird gut!

Anpacken von Aufgaben und Fortschritt innerhalb von https://github.com/pytorch/pytorch/issues/28619

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

eliabruni picture eliabruni  ·  3Kommentare

bartvm picture bartvm  ·  3Kommentare

soumith picture soumith  ·  3Kommentare

ikostrikov picture ikostrikov  ·  3Kommentare

keskarnitish picture keskarnitish  ·  3Kommentare