Pytorch: [RFC] دعم تنسيق الذاكرة (المعروف أيضًا باسم التخطيط المعروف باسم NHWC)

تم إنشاؤها على ١٠ أبريل ٢٠١٩  ·  68تعليقات  ·  مصدر: pytorch/pytorch

عرض المشكلة

يستخدم مشغلو CNN الترتيب المتعارف عليه لأبعاد الموتر ويخصصون لها معنى دلاليًا. بالنسبة للحالة ثنائية الأبعاد في PyTorch اليوم ، يجب أن يكون إدخال torch.nn.Conv2d موترًا رباعي الأبعاد بترتيب NCHW -.

لأسباب تتعلق بالأداء ، غالبًا ما يكون من المفيد إعادة ترتيب الأبعاد بشكل مختلف بحيث يتم وضع الذاكرة التي يتم الوصول إليها من خلال عمليات معينة بشكل متواصل ويتم استخدام المنطقة بشكل أفضل. الخيار الأكثر شيوعًا هو تحريك الأبعاد نحو النهاية - NHWC. يمكن أن تكون هناك تنسيقات ذاكرة أكثر تعقيدًا تقسم بُعدًا واحدًا إلى كتل ، على سبيل المثال.

تتضمن أمثلة المكتبات التي تستخدمها ما يلي:

  • تتمتع cudnn بأداء أسرع على Volta في NHWC
  • لا يدعم fbgemm و qnnpack NCHW.
  • يدعم libxsmm NCHW لكن عقوبة الأداء هي ما يقرب من 50٪ (IIRC).

يكمن التحدي في أن تحويل ترتيب البعد نفسه يعد مكلفًا ، لذلك في الحالات التي يتم فيها تنفيذ عمليات CNN متعددة على التوالي (على سبيل المثال conv(relu(conv))) ) ، من المفيد التحويل إلى تنسيق ذاكرة مختلف مرة واحدة ، وتنفيذ العمليات وإعادة ترتيبها الى الخلف.

وبالتالي ، من المهم جعل PyTorch على دراية بأوامر الأبعاد المختلفة والقدرة على تمرير الموترات بتنسيقات ذاكرة مختلفة بين العمليات في كل من الوضع النشط و JIT. علاوة على ذلك ، من المفيد الحصول على ممرات تحسين JIT تلقائية تحاول تطبيق الأساليب التجريبية أو تقنيات البحث لمعرفة ما إذا كان تغيير تنسيق الذاكرة مفيدًا بشكل مثالي وأين يكون من المنطقي القيام بذلك في النموذج.

نسعى جاهدين لبناء واجهة برمجة تطبيقات قادرة على تمثيل:

  • Tensor بتنسيق ذاكرة مختلف (في البداية ، فقط ترتيب الأبعاد) موجود في PyTorch في Eager و JIT. التنسيقات المحظورة ذات أولوية أقل لكنها لا تزال لطيفة.
  • واجهات برمجة التطبيقات التي يتعرض لها المستخدم للاستعلام عن تنسيق الذاكرة وتغييره
  • عمليات CNN الأساسية قادرة على التعامل مع موترات الإدخال بتنسيق ذاكرة مختلف وتوجيه لتنفيذ أسرع
  • القدرة على استنتاج وتحسين تنسيقات الذاكرة في ممرات JIT

المصطلحات : غالبًا ما يشار إلى المشكلة أعلاه باسم "تخطيط" (mxnet) ، "تنسيق البيانات" (tf) ، "image_format" (keras) ، "ترتيب" (caffe2). نقترح استخدام اسم "تنسيق الذاكرة" أو "memory_format" في PyTorch. لسوء الحظ ، تم أخذ الاسم "layout" في PyTorch بقيم "strided" مقابل "sparse_coo" ، لذلك لا يتوفر خيار التسمية هذا.

المشغلون المتأثرون

يجب أن يكون المشغلون التاليون على دراية بتنسيق الذاكرة على الأقل. بالإضافة إلى إنتاج النتيجة الصحيحة ، يحتاجون إلى تقديم أفضل أداء من المكتبات الأساسية والحفاظ على تنسيق ذاكرة المخرجات من أجل نشر هدف المستخدم المحدد صراحة.

  • التفاف
  • أنواع مختلفة من التجميع
  • معيار الدُفعة ، معيار الطبقة ، معيار المثال (بشكل عام ، مهما كانت المعايير)
  • الاختزال / الاستيفاء
  • ميزة التسرب
  • softmax إلى درجة أقل - يمكن تحديد البعد يدويًا هناك ، ولكن التطبيقات الفعالة موجودة فقط لتخطيط nchw الضمني
  • حشوة
  • عمليات عنصرية (أحادية وثنائية)
  • صانعي الموترات التي ترث تنسيق الذاكرة ، على سبيل المثال الفارغة.

API وتغييرات السلوك

حدد مفهوم تنسيق الذاكرة في PyTorch:

  • الثوابت مثل torch.memory_format.channels_first . ليس لديهم نوع محدد ويمكن أن يكونوا كائنات عشوائية قابلة للمقارنة (من المحتمل أن تبدأ بالتعداد ولكن في المستقبل قد تكون كائنات أخرى للتفاعل مع مفهوم موتر مسمى)

    • البديل: استخدم torch.channels_first مباشرة

  • القيم هي channels_first و channels_last (للسماح بعدد أقل من الثوابت)
  • بالنسبة للصور 1D / الموترات ثلاثية الأبعاد ، فإن القيم تعني NCW ، NWC ، للصور ثنائية الأبعاد / موتر 4D - NCHW ، NHWC ، للصور ثلاثية الأبعاد / موتر 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

  • تحافظ العمليات العكسية لعمليات CNN الأساسية على نفس تنسيق الذاكرة كما هو الحال في المسار الأمامي. (قد تكون هناك حاجة إلى أن يتم فرضها بشكل صريح لأن التدرجات الواردة للإخراج يمكن أن تكون بتنسيق ذاكرة مختلف)

تنسيق الذاكرة هو خاصية موتر يتم الاحتفاظ بها من خلال التسلسل / إلغاء التسلسل (في حالة كون الموتر معلمة).

تنفيذ متقدم

لدى Tensor في PyTorch اليوم مفهوم الخطوات التي تحدد كيفية وضع الموتر المنطقي في الذاكرة . على وجه التحديد ، يحتوي كل موتر على متجه strides بنفس طول sizes . من أجل فهرسة العناصر في الفهرسة المنطقية (i1, i2, .., ik) يقوم أحدهم بتقطيع المنتج بخطوات كبيرة ويبحث عن الذاكرة عند offset + i0*stride0 + i1*stride1 + ... * ik * stridek . وبالتالي ، فإن الموترات المتجاورة لها خطوات واسعة تعكس المنتجات التراكمية ذات الأحجام. على سبيل المثال ، موتر 4D بأحجام (n,c,h,w) لديه خطوات (c*h*w, h*w, w, 1) .

يمكن استخدام Strides لتمثيل تنسيقات ذاكرة مختلفة (إعادة ترتيب الأبعاد) فعليًا مع الحفاظ على ترتيب 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()

إيجابيات هذا النهج:

  • يستخدم مفهوم PyTorch الحالي للخطوات دون إضافة أفكار جديدة عالية المستوى أو معلمات API
  • يحافظ على السلوك المنطقي للموتر في ترتيب NCHW الكنسي
  • يعمل على إعادة الترتيب التعسفي لأبعاد الإدخال
  • تحافظ إجراءات التسلسل الحالية بالفعل على خطوات كبيرة في الموتر
  • القدرة على إعادة استخدام العديد من العمليات للعمل على تخطيط ذاكرة مختلف

سلبيات :

  • استدعاء .contiguous() يعادل التبديل إلى NCHW وقد يحدث عن طريق الصدفة من المستخدم أو داخل أحد العمليات

    • هناك حاجة إلى مراجعة صريحة للمشغلين للتأكد من أنهم يحافظون على تنسيق الذاكرة

  • لا يعمل مع التنسيقات المحظورة / المتجانبة - هناك حاجة إلى نهج مختلف

    • من الممكن التفكير في إضافتهم كمواطنين من الدرجة الأولى في 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" للإشارة إلى عوامل التشغيل في JIT IR بدلاً من إنشاء عوامل تشغيل منفصلة مثل "conv_nhwc". والسبب في ذلك هو البساطة والحفاظ على IR على مستوى التمثيل الدلالي.

العمليات الحكيمة

علينا التأكد من أن العمليات الأساسية مثل العناصر الحكيمة تحافظ على تنسيق الذاكرة وفعالة.

يمكن معالجة العمليات الأحادية بشكل عام عن طريق التحقق مما إذا كانت كتلة من الذاكرة "كثيفة" - أي ما إذا كانت العناصر تمتد على منطقة خالية من الفجوات ويتم استخدام كل موقع ذاكرة مرة واحدة بالضبط. يمكن التحقق من ذلك باستخدام خوارزمية بسيطة

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 ويطبق على موتر 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

الاقتراح الحالي يجب أن يكون:

  • لا توجد معالجة من الدرجة الأولى لتنسيق الذاكرة في التعليقات التوضيحية للكتابة حتى الآن. بدلاً من ذلك ، يمكننا الحفاظ على خريطة lookaside بالشكل الضروري للممرات التي تتلاعب بتنسيق الذاكرة
  • تمرير الاستدلال (مشابه لـ shape_inference) الذي ينتج تعليقات توضيحية بتنسيق لكل قيمة
  • تمريرات تحويل تنسيق الذاكرة (يدويًا أو آليًا) والتي تجد عند الضرورة إدخال مكالمات to(memory_format) للحصول على الأداء الأمثل

لأغراض التنفيذ ، يمكننا أيضًا استخدام عبارات مثل assert x.is_contiguous(channels_last) .

ملاحظة: هناك سؤال حول مكان تخزين المعلومات ، حيث يحتوي جهاز معين على مجموعة تنسيق ذاكرة مفضلة (على سبيل المثال ، qconv على مسارات x86 إلى fbgemm التي تنفذ NHWC فقط). أحد الخيارات هو وضعه في مستوى تسجيل المرجع ، ومع ذلك ، يبدو التعليق التوضيحي لتنسيق الذاكرة وكأنه معلومات جانبية. يمكننا أن نبدأ بالحفاظ على خريطة عالمية في مكان ما في JIT pass التي تشير إلى تنسيقات الذاكرة المفضلة والاستدلالات المرتبطة بها. إذا أصبح غير مرتب - يمكننا التبديل إلى آلية قائمة على التسجيل.

بعد: التخطيطات المحظورة

نظرًا لأننا قررنا إضافة عبوات أكثر تعقيدًا من الموترات ، فقد لا يكون استخدام موتر PyTorch من الدرجة الأولى لأنه قد لا يكون معقولاً بسبب تكلفة التنفيذ العالية والتعقيد. هناك بديلان محتملان:

  • تمثيلات مبهمة مثل الارتباطات المخصصة من النوع C. هذا خيار للاختيار من أجل التعبئة في الاستدلال حيث يكون التنوع أعلى من حيث تحسينات الأداء
  • نوع موتر من الدرجة الأولى مثل MKLDNNTensor مع بعض (وليس كل) العمليات المرتبطة بهذا النوع الجديد

هناك بديل آخر وهو تنفيذ الدعم الأصلي للحظر / التبليط في فئة PyTorch Tensor الأساسية.

علاقة موتر مسمى

تم تصميم الاقتراح الحالي لـ NamedTensor كآلية للتحقق من النوع على الموترات - في الوقت الحالي لا يقوم بتعيين أي معنى دلالي لأسماء الأبعاد. وبالتالي فإن الطريقة الوحيدة لاستنتاج معنى موتر التنشيط هي الاستمرار في استخدام تنسيق NCHW المحدد مسبقًا. يجعل NamedTensor والمقترحات الحالية متعامدة.

إذا كنا على استعداد لتحديد معاني بعض الأسماء بدقة (مثل "القنوات" و "العروض") ، فيمكن للمشغلين استخدام هذه المعلومات للتوجيه إلى تنفيذ أسرع. سيكون تغييرًا دلاليًا على الرغم من أن موترات الإدخال سيكون لها بشكل منطقي تنسيق ذاكرة NHWC (وليس NCHW كما هو الحال اليوم).

فن مسبق

يدعم TensorFlow كلاً من NHWC و NCHW على مستوى المشغل ، عبر المعلمة data_format ؛ القيم المقبولة هي ("NHWC" ، "NCHW") لمدخلات 4-d ، ("NDHWC" ، "NCDHW") لمدخلات 5-d ، أو channels_first / channels_last مستقل عن المدخلات الأبعاد. الأمر متروك للمستخدم للتعامل مع إعداد المعلمة بشكل صحيح ، أي لا يتم تتبعها تلقائيًا بواسطة الموتر.

يستدعي Caffe2 هذه المعلمة تسمى order بدلاً من data_format ، لكنها لا تزال مطبقة على مستوى المشغل الفردي بشكل صريح.


الملحق: النظر في الخيارات الأخرى

سؤال Litmus: ماذا يطبع الكود التالي: tensor_in_nhwc_layout.size(1) - عدد القنوات (لأن الافتراضي هو NCHW في PyTorch) أو الارتفاع (لأن هذا ما هو موجود في تخطيط NHWC في الموضع 1).

بناءً على هذه الإجابة ، هناك عدة خيارات ممكنة:

  • الخيار أ - الخطوات (المعروضة أعلاه). تخطيط الموتر هو تمثيل داخلي بالكامل. التنفيذ مثله هو الأكثر ملاءمة مع خطوات كبيرة.

    • .size (1) يعيدني "القنوات" ، لكن الذاكرة الداخلية وضعت بشكل مختلف

    • المؤيد: لا يغير كود النموذج ، لا يزال نموذجي قادرًا على حساب الأبعاد مباشرة. في الواقع لم يتغير أي من واجهة برمجة التطبيقات العامة

    • السلبيات: في التنفيذ الخطى ، يستدعي العديد من المشغلين .contiguous () ويمكنهم إعادة التصميم مرة أخرى عن طريق الخطأ

    • السلبيات: من منظور المستخدم ، فهم أهمية ضمانات عودة المرجع. تلغي IMO نهج الخطوات فقط ، لأنه يصبح من الصعب جدًا فهم التنسيق الذي سيتم إرجاع مرجعك فيه ، ولا توجد واجهة برمجة تطبيقات لقول "تجاهل خطواتي ، في الواقع فقط قم بإرجاع الشيء المجاور لـ NCHW." هذا بالإضافة إلى القيود المذكورة أعلاه.

  • الخيار ب - موتر صريح NHWC. يتلاعب المستخدم صراحة بالموتر الذي له ترتيب أبعاد مختلف لكن الموتر نفسه لا يعرف أي شيء عنه. سنحتاج إلى بعض التعليقات التوضيحية على مستوى المشغل لمعرفة ما يتوقعه المستخدم.

    • .size (1) تعرض "الارتفاع"

    • المؤيد: لا يوجد سحر ويمكن التنبؤ به للغاية

    • سلبيات: يصبح تغيير النموذج من تخطيط إلى آخر عملية معقدة تحتاج إلى تتبع جميع عمليات الوصول إلى .size () و .reshape () (أو تحتاج إلى توضيحها في API؟)

  • الخيار ب '- موتر NHWC صريح مع علم تخطيط . كما هو مذكور أعلاه ، لكننا نسمح بإرفاق تعليق توضيحي بالموتر لتمييز تخطيطه الدلالي الذي تستهلكه العمليات في تنفيذها. ليست هناك حاجة إلى التعليق التوضيحي على مستوى المشغل بعد ذلك - يمكن للمشغل أن يقوم بالإرسال استنادًا إلى علامة تخطيط المدخلات.
  • الخيار ج - يسمى موتر . ( https://docs.google.com/document/d/1ynu3wA2hcjwOtEng04N904gJjEbZWcINXO_ardX6hxc/edit#heading = h.2gbe5xpga3w9)

    • .size (1) يعرض "الارتفاع" ولكننا نطلب من الأشخاص عدم استخدام واجهة برمجة التطبيقات هذه واستخدام .size ("القناة") بدلاً من ذلك

    • المؤيد: صريح جدا وما يريده المستخدم

    • con: لا يحل مشكلة الانتقال ، سنحتاج إلى إجبار كل التعليمات البرمجية المكتوبة بإدراك التخطيط لاستخدام الموترات المسماة. إذا لم يكن كذلك - تنطبق نفس المشاكل المذكورة أعلاه

  • الخيار D - التخطيط هو نوع موتر معتم . نتعامل مع NHWC كما نتعامل مع MKLDNN أو SparseTensor - نوع موتر منفصل بمعرّف إرسال مختلف. إنه مثل الخيار A ولكن مع مقايضات مختلفة على السلوك الافتراضي - قد تفشل العمليات التي لم يتم تنفيذها بدلاً من الرجوع إلى NCHW.

    • .size (1) لا يزال يعرض "القنوات"

    • المؤيد: لا يوجد سحر وصريح ، إرسال منفصل يسمح للعمليات بتحديد ما يريدون

    • الإيجابيات / العيوب: يجب تنفيذ جميع المشغلين الضروريين على تخطيط مختلف ، إذا كان بعض العمليات مفقودة ، فسيحصل المستخدم على خطأ صريح بأنه غير مدعوم

    • السلبيات: ربما نحتاج إلى حظر العديد من العمليات عليه ، على سبيل المثال المشاهدات لأنه من الصعب التنبؤ بالنتائج المتوقعة

internals mkldnn triaged

التعليق الأكثر فائدة

راجع للشغل لماذا يتعين علينا إنشاء مفهوم جديد بدلاً من مجرد التمسك بـ layout ؟ لا أعتقد أن التمثيلات المتفرقة لها مفهوم محدد جيدًا للتنسيق مثل "channels_last" ، لذلك لا نحتاج إلى تمثيل منتج memory_formats * layouts (يشير layouts إلى الاستخدام الحالي ) ، ولكن فقط memory_format + layouts يعني أنه من الجيد استخدام نفس الوسيطة كما اعتدنا؟ بالنسبة لي ، فهي أقصر وأجمل وستسمح لنا بتجنب توسيع نطاق توقيعات المصانع إلى آلاف الحجج.

ال 68 كومينتر

هناك مشكلة واحدة مع empty_like ؛ الدلالات المحددة حاليًا هي أنك تقوم بإسقاط جميع معلومات الخطوات ، لذلك ، لا يمكن الحفاظ على التخطيط وأن تكون BC.

تم تسجيلVitalyFedyunin لتنفيذ .contiguous() و torch.memory_layout بت

سؤال واحد - للموتر رباعي الأبعاد 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) سيعود صحيح؟

هذا صحيح. ومع ذلك ، لا يمكننا الاعتماد على الخطوات الكبيرة فقط لأننا نريد تجنب أي تحويلات إلى الوراء وإعادة التوجيه أثناء النسخ والعمليات المماثلة.

ماذا لو كانت الخطوات لها نفس ترتيب تنسيق الذاكرة؟ دعنا نستخدم موتر 4D كمثال. لوصف موتر ، لدينا sizes ، strides و stride_indexes :

الأحجام في (n، c، h، w)
خطوات في الترتيب المادي ، أي

  • خطوات (n ، c ، h ، w) إذا كان التنسيق هو nchw
  • خطوات (n ، h ، w ، c) إذا كان التنسيق nhwc.

خرائط 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 ، سيكون هذا بعض العمل هنا.

بدلاً من ذلك ، يمكننا أيضًا تغيير النموذج الأولي قليلاً ، على سبيل المثال

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

وبالتالي ، فإنه يفترض بشكل افتراضي أن nchw فقط متجاور. بهذه الطريقة لا تحتاج إلى إعادة كتابة OPs ، سيتم إعادة ترتيبها إلى nchw تلقائيًا.

نسعى جاهدين لبناء واجهة برمجة تطبيقات قادرة على تمثيل:

  • Tensor بتنسيق ذاكرة مختلف (في البداية ، فقط ترتيب الأبعاد) موجود في PyTorch في Eager و JIT. التنسيقات المحظورة ذات أولوية أقل لكنها لا تزال لطيفة.
  • واجهات برمجة التطبيقات التي يتعرض لها المستخدم للاستعلام عن تنسيق الذاكرة وتغييره
  • عمليات CNN الأساسية قادرة على التعامل مع موترات الإدخال بتنسيق ذاكرة مختلف وتوجيه لتنفيذ أسرع
  • القدرة على استنتاج وتحسين تنسيقات الذاكرة في ممرات JIT

اقتراح رائع! هل يمكنني توضيح فهمي لمعرفة ما إذا كان ذلك صحيحًا (بما في ذلك المقترحات الخاصة بمعالجة تنسيقات MKL-DNN):

اسمحوا لي أن أعتقد أنه كان هناك تنفيذ لهذا الاقتراح كفئة "تنسيق". طالما أنها توفر الاستعلام عن واجهة برمجة التطبيقات وتغييرها على أنها افتراضية ، فيمكننا القيام بالوراثة / الامتدادات التي تناسب تنسيقات MKL-DNN المعقدة. أو طرق أخرى طالما أنها توفر إطارًا للتعامل مع التنسيقات ، وتفريغ تلك التفاصيل الدقيقة لنا.

حول تنفيذ OPs ، يمكن أن يكون لكل OP تنسيقات مفضلة تعمل على الحد الأقصى من أدائها وتنسيق متوافق يعمل. عامل الحكمة (أو بشكل عام ، OPs المقيدة بالذاكرة) يفترض أنه ليس له أي تفضيل. OP ينتج موتر نتائجه مع كائن "تنسيق" ، يضمن كائن التنسيق هذا الاستعلام / تغيير الدلالات المتوافقة مع توقعات pytorch الافتراضية ، بالإضافة إلى أنه يمكنه التعامل مع تنسيقات محددة إذا تم استدعاء سلسلة من الوظائف المحسّنة (مثل conv2d (ReLU (conv2d)) قضية)

uyongw أريد أن أوضح قليلاً عن

بعبارة أخرى ، لا يوجد معنى جوهري للأبعاد المادية للموتر (عندما نتجاهل الخطوات). نحن نعطيها معنى فقط عندما نفكر في كيفية الإشارة إليها فيما يتعلق بالخطوات الواسعة.

لوصف موتر ، لدينا الأحجام والخطوات والخطوات

أعتقد أن stride_indexes طريقة ملائمة للتفكير في المشكلة ، لكنها لا لزوم لها تمامًا مع الخطوات ، لأن كل ما تقوله هو "تطبيق هذا التبدل (العكسي؟) على الخطوات ، ثم تعامل مع ذلك على أنه خطوات حقيقية.) كنت أتحدث أنا و VitalyFedyunin عن كيفية تخزين هذه المعلومات مؤقتًا بطريقة ما ، لأنه من الصعب إعادة بناء المعلومات من الخطوات نفسها. لكن هذا خارج نطاق هذا الاقتراح.

وبالتالي ، فإنه يفترض بشكل افتراضي أن nchw فقط متجاور.

نعم ، هذه هي قراءتي للخطة.

تضمين التغريدة

اسمحوا لي أن أعتقد أنه كان هناك تنفيذ لهذا الاقتراح كفئة "تنسيق". طالما أنها توفر الاستعلام عن واجهة برمجة التطبيقات وتغييرها على أنها افتراضية ، فيمكننا القيام بالوراثة / الامتدادات التي تناسب تنسيقات MKL-DNN المعقدة. أو طرق أخرى طالما أنها توفر إطارًا للتعامل مع التنسيقات ، وتفريغ تلك التفاصيل الدقيقة لنا.

لا أعتقد في الواقع أن هذا وصف دقيق للاقتراح. دعم تخطيط الذاكرة الذي يدعمه الاقتراح هنا هو فقط التخطيطات التي يمكن التعبير عنها من خلال الخطوات الكبيرة. أي شيء يتعذر التعبير عنه بهذه الطريقة (على سبيل المثال ، تخطيط الكتلة) لن يعمل بهذه الطريقة ، ويجب أن يكون مدعومًا بآلية "التخطيط" ذات الوزن الثقيل.

بعبارة أخرى ، لا يوجد معنى جوهري للأبعاد المادية للموتر (عندما نتجاهل الخطوات). نحن نعطيها معنى فقط عندما نفكر في كيفية الإشارة إليها فيما يتعلق بالخطوات الواسعة.

أوافق جزئيًا :-) لكن ليس بشأن هذه المشكلة تحديدًا. لنفترض أن لدي بالفعل موتر nhwc. ثم أقوم بتغييره إلى nwhc. أريد أن أتبدل إلى nhwc ثم أقوم بعمل مجاور (). لكنني حصلت عليه nhwc متجاورة بالفعل. أليس هذا محيرا؟

أعتقد أن stride_indexes طريقة مناسبة للتفكير في المشكلة ، لكنها زائدة عن الحاجة تمامًا مع الخطوات ، لأن كل ما تقوله هو "تطبيق هذا (عكس؟) التقليب على الخطوات ، ثم التعامل مع ذلك باعتباره الخطوات الحقيقية.)

IMHO ، لن تكون زائدة عن الحاجة مع الخطوات الكبيرة ، إذا كانت لديك خطوات كبيرة في nhwc (المادية). لأنك بحاجة إلى التعيين الصحيح بالأحجام (المنطق). وإلا فلا توجد طريقة لمعرفة الترتيب الحقيقي.

راجع للشغل هناك نهج أكثر وضوحًا باستخدام رسم الخرائط العكسي. لنفترض أن nchw هي (0، 1، 2، 3) ، بالنسبة إلى nhwc ، تكون (0 ، 3 ، 1 ، 2) بدلاً من (0 ، 2 ، 3 ، 1). هذا يقول أن مؤشر stride_index نفسه هو دائمًا NCHW أيضًا. لكن المشكلة هي أنه لا يمكن توسيعه ليشمل التنسيقات المحظورة مثل nChw16c أو OIhw16i16o.

تتطلب التنسيقات المحظورة مجموعة مختلفة تمامًا من تنفيذ المشغلين ؛ لهذا السبب ، نفضل عدم مزجها بـ "تنسيق الذاكرة" ، والذي من المفترض أن يكون ودودًا مع جميع المشغلين الحاليين ويعمل بأداء مماثل أو أفضل.

أوافق جزئيًا :-) لكن ليس بشأن هذه المشكلة تحديدًا. لنفترض أن لدي بالفعل موتر nhwc. ثم أقوم بتغييره إلى nwhc. أريد أن أتبدل إلى nhwc ثم أقوم بعمل مجاور (). لكنني حصلت عليه nhwc متجاورة بالفعل. أليس هذا محيرا؟

من الصعب فهم مثالك لأنك تستخدم بعض المصطلحات بالعامية والدقة مطلوبة. إليك كيف أفسر ما قلته:

  • يجب أن يكون موتر "nhwc" وفقًا لهذا الاقتراح ، "Tensor الذي يكون تخطيطه المادي هو 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 متجاورًا.

لا أعتقد أن هذا هو المثال الذي كان يدور في ذهنك ، وفي هذه الحالة يجب أن تكون أكثر دقة بشأن ما تقصده بكلمة "التناوب".

IMHO ، لن تكون زائدة عن الحاجة مع الخطوات الكبيرة ، إذا كانت لديك خطوات كبيرة في nhwc (المادية). لأنك بحاجة إلى التعيين الصحيح بالأحجام (المنطق). وإلا فلا توجد طريقة لمعرفة الترتيب الحقيقي.

وهذا هو جوهر الاقتراح: نحن امتياز NCHW كما تخطيط منطقي، دائما. لذلك إذا كان لدي موتر رباعي الأبعاد لا أعرف عنه شيئًا ، أفترض أن تخطيطه المنطقي هو NCHW. هذا يزيل الغموض. إذا كنت ترغب في التعامل مع الموترات التي لا يكون تخطيطها المنطقي NCHW ، أعتقد أن واجهة برمجة التطبيقات كما هو مذكور تجعل الحياة صعبة بعض الشيء بالنسبة لك.

تضمين التغريدة

تحافظ العمليات على سلوك تنسيق الذاكرة

إذا كان من الممكن حدوث موترات NHWC المادية بحتة من خلال خطوات واسعة ، فهذا من الناحية الفنية كسر BC ، ما لم تجعلها تحافظ فقط على تنسيق الذاكرة عندما تكون علامة تنسيق الذاكرة موجودة (ولكن يبدو أنك لا تريد أن يكون لهذا معنى دلالي ، لذلك أنا لست متأكدًا مما يقترحه الاقتراح حاليًا.) لست متأكدًا مما إذا كان هذا يكسر بالفعل رمز أي شخص في الممارسة العملية.

إذا كان من الممكن حدوث موترات NHWC المادية بحتة من خلال خطوات واسعة ، فهذا من الناحية الفنية كسر BC ، ما لم تجعلها تحافظ فقط على تنسيق الذاكرة عندما تكون علامة تنسيق الذاكرة موجودة (ولكن يبدو أنك لا تريد أن يكون لهذا معنى دلالي ، لذلك أنا لست متأكدًا مما يقترحه الاقتراح حاليًا.) لست متأكدًا مما إذا كان هذا يكسر بالفعل رمز أي شخص في الممارسة العملية.

بافتراض أنه يمكننا جعل تنسيق الذاكرة "ثابتًا". سيؤدي تشغيل موتر تنسيق الذاكرة إلى إنتاج موتر مهيأ للذاكرة. هذا سوف يحل مشكلة BC.

ومع ذلك ، نحتاج إلى تحديد سلوك العمليات الثنائية (أو المزيد من الأعضاء) عندما يكون للموترات تنسيقات ذاكرة مختلفة.

ezyang أوه لقد وجدت للتو أن هناك خطأ مطبعي في ردي أعلاه. (أنا آسف لذلك. ومع ذلك ، فإن المثال الأصلي لا يزال صحيحًا.) دعني أعيد تأكيده على النحو التالي:

  1. لدي موتر NCHW (جسديًا ، متجاور).
  2. ثم أقوم بتغييره إلى NWHC (منطقيًا).
  3. أريد أن أغيره أكثر إلى NHWC مع اتباع استدعاء () متصل.
  4. استخدمه باعتباره NHWC (جسديًا).

لكنني حصلت عليه NHWC متجاور بالفعل بعد الخطوة 2. ثم يمكنني تخطي الخطوة 3 واستخدامه كـ NHWC مباشرةً في الخطوة 4. لكن هذا بالتأكيد غير صحيح لأن الترتيب المادي للموتر لا يتغير على الإطلاق.

تتطلب التنسيقات المحظورة مجموعة مختلفة تمامًا من تنفيذ المشغلين ؛ لهذا السبب ، نفضل عدم مزجها بـ "تنسيق الذاكرة" ، والذي من المفترض أن يكون ودودًا مع جميع المشغلين الحاليين ويعمل بأداء مماثل أو أفضل.

نعم يمكننا تمكين NHWC كخطوة أولى. ومع ذلك ، لا أعتقد أن التنسيق المحظور هو شيء مختلف تمامًا. يمكن التعبير عنها بشكل طبيعي (مع بعض التجريد الجيد). إذا كان هناك وصف عام للتنسيق ، فيمكن للآخرين تسجيل التنسيقات الجديدة باستخدام الحظر / الخطوات التعسفية.

أكثر إذا قمنا بحظر الدعم بالفعل ، فإننا لا نهتم بإنشاء بعض التركيبات المخفية لتشغيل كل شيء أساسي ، مما يخلق عالمًا ضمنيًا بالداخل وقد يصبح "من / إلى بين العالمين" مشكلة.

على أي حال ، قد يكون التفكير في التنسيق المحظور بعيدًا جدًا. لكنني أعتقد أنه إذا كان ذلك ممكنًا ، فمن الأفضل جعل التصميم قابلاً للتوسيع.

لكنني حصلت عليه NHWC متجاور بالفعل بعد الخطوة 2. ثم يمكنني تخطي الخطوة 3 واستخدامه كـ NHWC مباشرةً في الخطوة 4. لكن هذا بالتأكيد غير صحيح لأن الترتيب المادي للموتر لا يتغير على الإطلاق.

حسنًا ، أنا أفهم مثالك الآن. يمكنك بالفعل التوقف عند الخطوة 2 واستخدامها كما لو كانت موتر NCHW ؛ في هذه الحالة ، سوف تفسر بشكل غير صحيح W على أنها C ، وما إلى ذلك. وهذا بالتأكيد جانب سلبي في التنفيذ المستند إلى الخطوة ( dzhulgakov ، ربما ينبغي أن نضيف هذا إلى الاقتراح). يتضمن الاقتراح بعض الأحكام لهذه الحالة:

لحل المشكلة المذكورة أعلاه ، فإن الاقتراح الأولي هو تقديم علامة تنسيق ذاكرة "ناعمة" على موتر تسجل آخر استدعاء (memory_format) تم إجراؤه على موتر. سيحتاج المشغلون إلى نشر هذا التعليق التوضيحي للمخرجات. التعليقات التوضيحية "ناعمة" ، لذلك لن نخطئ في عدم تطابق التعليقات التوضيحية ولكن بدلاً من ذلك ننتج تحذيرات في وضع التنميط.

تسمح لك علامة تنسيق الذاكرة الناعمة بالتمييز عن موتر NCHW الذي قمت بتبديله ، مقابل الموتر الذي هو في الواقع NHWC. لكن العلامة اللينة في شكلها الحالي غير ملزمة ، لذلك لست متأكدًا من مدى فائدتها في هذه الحالة.

هناك طريقة أخرى لحل المشكلة وهي باستخدام الموترات المسماة. باستخدام الموترات المسماة ، يمكننا استخدام الأسماء على الأبعاد (المنطقية) لمعرفة ما إذا كنا نشاهد موترًا على أنه NCHW (الافتراضي المفترض) أو أي شيء آخر.

ومع ذلك ، لا أعتقد أن التنسيق المحظور هو شيء مختلف تمامًا. يمكن التعبير عنها بشكل طبيعي (مع بعض التجريد الجيد). إذا كان هناك وصف عام للتنسيق ، فيمكن للآخرين تسجيل التنسيقات الجديدة باستخدام الحظر / الخطوات التعسفية.

يوجد المزيد من التعليقات حول الموضوع هنا: https://github.com/pytorch/pytorch/issues/16038#issuecomment -454490374

ezyang شكرا على الرد. نعم علامة التنسيق الناعمة قد تساعد. القلق هو أنه قد لا يكون مرنًا بما يكفي لأن ترتيب البعد يمكن أن يكون تعسفيًا. كما أنها نفسها غير قابلة للحساب. الموتر المسمى له معنى دلالي لكل بُعد ، لكن قد يحتاج إلى بعض التسهيلات الأخرى لدعم أشك.

أنا شخصياً أعتقد أن هذا يمكن حله عن طريق تقديم خريطة من ترتيب الخطوات (المادية) إلى ترتيب أحجام NCHW (منطقي). كما اقترحت أعلاه ، بالنسبة لـ NCHW هو نفس التصميم الحالي تقريبًا ؛ بالنسبة إلى NHWC ، لا يزال sizes NCHW ، strides سيكون بالترتيب (N ، H ، W ، C). ونستخدم stride_index = (0، 2، 3، 1) لتحديد مؤشر أبعاد الخطوات.

علاوة على ذلك ، يمكن استخدام مجموعة strides و stride_index لتمثيل أي تنسيق موتر. قد يمنح هذا مرونة للآخرين لتسجيل تنسيق بيانات جديد.

تضمين التغريدة

تحافظ العمليات على سلوك تنسيق الذاكرة

إذا كان من الممكن حدوث موترات NHWC المادية بحتة من خلال خطوات واسعة ، فهذا من الناحية الفنية كسر BC ، ما لم تجعلها تحافظ فقط على تنسيق الذاكرة عندما تكون علامة تنسيق الذاكرة موجودة (ولكن يبدو أنك لا تريد أن يكون لهذا معنى دلالي ، لذلك أنا لست متأكدًا مما يقترحه الاقتراح حاليًا.) لست متأكدًا مما إذا كان هذا يكسر بالفعل رمز أي شخص في الممارسة العملية.

عندما تم نقل العمليات الحسابية والعتبة إلى TensorIterator ، كان ذلك من الناحية الفنية كسرًا لـ BC (لأن تنسيق ذاكرة المعاملات كان لا يتم الاحتفاظ به ، ويحافظ TensorIterator عليه). الوضع الراهن غير متسق للغاية الآن - العتبة تحافظ على التخطيط ، وجميع العمليات الأحادية الأخرى لا تفعل ذلك ، torch.where لا ، العمليات الحسابية تحتفظ بالتخطيط إذا كان كلا المعاملين لهما نفس التخطيط ، ولكن سيكون افتراضيًا على "nchw" أو موتر contiguous في الفهم الحالي إذا كان هناك عدم تطابق ، لست متأكدًا مما يحدث للبث.
أنت أيضًا تقوم بعمل نقطة جيدة حول empty_like وما شابه ذلك من تنسيق الحفاظ على أنه ليس BC. ربما سيحتاج أيضًا إلى وسيطة تخطيط ، مثل is_contiguous في الاقتراح

x.is_contiguous(torch.memory_format.channels_first)

تضمين التغريدة

هناك مشكلة واحدة مع blank_like ؛ الدلالات المحددة حاليًا هي أنك تقوم بإسقاط جميع معلومات الخطوات ، لذلك ، لا يمكن الحفاظ على التخطيط وأن تكون BC.

أنت أيضًا تشير إلى نقطة جيدة حول أن تنسيق الحفاظ على فارغ وما شابه ليس BC.

وإذا كنا لا تعتمد على خطوات للتعبير عن الترتيب الفعلي، empty_like لا ليس من الضروري كسر قبل الميلاد. هناك 3 أنواع من معلومات الأبعاد في الموتر:

  • الشكل: الأحجام
  • ترتيب منطقي: معلومات الطلب مسجلة في خطوات (تستخدم عادة لدعم التحويل أو التبديل)
  • الترتيب المادي: NCHW أو NHWC (يمكن معالجته على شكل stride_index كما اقترحت).

الترتيب المادي حاليًا هو نفس الشكل / الأحجام. لذلك نحن فقط نسقط ترتيب المنطق بخطوات كبيرة. ضع في اعتبارك أننا نفصل الشكل عن الترتيب المادي ، يمكننا أيضًا إسقاط الترتيب المنطقي مع الحفاظ على الشكل والنظام المادي مقابل empty_like . هذا يعني أنه سيتم الاحتفاظ بكل من size() و stride_index() ، ولكن ستتم إعادة تعيين stride() . على وجه الخصوص ، سيعيد موتر NHWC empty_like من موتر NHWC موترًا متجاورًا NHWC مع نفس معلومات الشكل المحددة.

uyongw لست متأكدًا من أنه سيكون من الجيد تغيير empty_like ؛ تتطابق دلالاتها الآن مع Numpy empty_like .

الوضع الراهن غير متسق للغاية الآن - العتبة تحافظ على التخطيط ، وجميع العمليات الأحادية الأخرى لا تفعل ذلك ، torch.where لا ، العمليات الحسابية تحافظ على التخطيط إذا كان كلا المعاملين لهما نفس التخطيط ، ولكن سيكون افتراضيًا على "nchw" أو موتر متجاور في الفهم الحالي إذا كان هناك عدم تطابق ، لست متأكدًا مما يحدث للبث.

ngimel ، نعم ، هذه ليست متسقة جدًا في الوقت الحالي. أعتقد أن جزءًا من كيفية تمثيل تنسيق الذاكرة هو جعل المشغلين لدينا في حالة متسقة

@ zou3519 فارغ_لايك في numpy الذي قمت order التي تتطابق افتراضيًا مع تخطيط النموذج الأولي قدر الإمكان. هذا ليس ما يفعله empty_like في pytorch حاليًا (يُرجع "nchw" - موتر قريب ، حتى إذا كان النموذج الأولي غير متجاور)

أوه ، كما أرى ، كنت أقرأ ذلك بسرعة كبيرة. في هذه الحالة ، سيكون من الجيد أن يكون لدينا أرقام مطابقة فارغة أيضًا ، وسيكون من الجيد (على الأرجح؟) وجود تخطيط للذاكرة هنا أيضًا

@ zou3519 نعم ما أحاول أن أقوله هو للحفاظ على دلالات الحالية (انخفاض ترتيب منطقي كماezyang وngimel المذكورة)، وفي نفس الوقت الحفاظ على التصميم المادي مثل التخلف نمباي ل. وبالتالي بالنسبة للنموذج الأولي NCHW ، سيكون السلوك كما كان من قبل. بالنسبة لنموذج NHWC الأولي ، سيظل سلوكه متوافقًا ، أي أن الموتر الجديد سيكون متاخمًا لـ NHWC ، بدلاً من NCHW متجاور إذا لم تقم بتغيير التنفيذ الحالي.

سؤالين:

  • ماذا يحدث إذا تمت إضافة موتر NHWC إلى موتر NCHW؟
  • ماذا عن معالجة عيب (B) من خلال إنشاء طرق مثل t.channel_dim () على موتر تعيد القيمة الصحيحة التي تشير إلى مكان البعد ماديًا؟ قد يكون هذا النهج مطلوبًا للسماح باختيار تنسيقات أخرى ، مثل تنسيقات الكتلة ، بدون تغييرات في الشبكة.

إذا عالجنا مخالفة (ب) بالنقطة الأخيرة ، فإن (ب) تبدو أفضل بالنسبة لي. إنه واضح بشكل بديهي ومن السهل اكتشاف الأخطاء المنطقية. يمكن أن تعمل جميع العمليات الحالية على الموتر أيضًا ، نظرًا لأنها تبدو مثل أي موتر آخر متجاور. العمليات التي يمكنها فهم الدلالات (مماثلة لاقتراح موتر مسمى) ستعمل كما هو متوقع أيضًا.

@ zou3519 فارغ_لايك في numpy الذي قمت order التي تتطابق افتراضيًا مع تخطيط النموذج الأولي قدر الإمكان. هذا ليس ما يفعله empty_like في pytorch حاليًا (يُرجع "nchw" - موتر قريب ، حتى إذا كان النموذج الأولي غير متجاور)

نحن نخطط للحفاظ على التنسيق في مثل هذه الحالات (لموترات تنسيق الذاكرة)

ماذا يحدث إذا تمت إضافة موتر NHWC إلى موتر NCHW؟
ستؤدي العملية باستخدام موتر مهيأ إلى الذاكرة إلى إرجاع موتر منسق للذاكرة. إذا كان كلا الموتران عبارة عن ذاكرة بتنسيق الإخراج ، فسيتم تحديده بواسطة الموتر الأول.

شيئين أود أن أضيف:

نحن نخطط للحفاظ على التنسيق في مثل هذه الحالات (لموترات تنسيق الذاكرة)

سنحتاج إلى تدقيق الاستخدامات الحالية ، لأنه غالبًا ما يتصل المشغلون بـ empty_like ثم يفترضون أنهم متجاورون NCHW. وأنا لا أعرف كيف سنتعامل مع كود الطرف الثالث. يبدو أننا سنحتاج إلى تقصير مختلف عن numpy إذا أردنا الحفاظ على BC.

ستؤدي العملية باستخدام موتر مهيأ إلى الذاكرة إلى إرجاع موتر مهيأ للذاكرة. إذا كان كلا الموتران عبارة عن ذاكرة بتنسيق الإخراج ، فسيتم تحديده بواسطة الموتر الأول.

أود أيضًا أن أضيف ، إذا كنت تهتم حقًا بالتنسيق الذي يأتي به الإخراج الخاص بك - قم بتمرير موتر الإخراج.

اتفق على blank_like ، هناك عدد قليل جدًا من الحالات التي يُفترض فيها أن نتيجة الفارغة مثل / zeros_like وما إلى ذلك متجاورة (يجب أن أقول أنها متجاورة جسديًا ، في كثير من الحالات ليست عمليات صورة).
لا يعد تمرير موتر الإخراج خيارًا في معظم الحالات ، لأن الوظائف ذات out kwarg لا يمكن تفاضلها.

تأتي العديد من مشاكلنا من عدم تناسق تخطيطات الإخراج المتوقعة. لا يمكننا حلها جميعًا مرة واحدة ، ولكن يمكننا محاولة قفل الحالة الحالية (على الأقل للخطوات الكبيرة) وتثبيتها واحدة تلو الأخرى. إذن هذا هو الاقتراح.

واجهة برمجة تطبيقات Python

أدخل 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)

الآن حول blank_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 افتراضيًا وفقًا للحالة الحالية للعالم ، وتصنيفها وإدراك كيفية تغييرها في المستقبل.

إذا قمت بتحديد tensor ، فسيتم تجاهل TensorOptions حاليًا (في أفضل الأحوال ، يتم استثناء خيار الجهاز الذي تم تمريره مع جهاز tensor 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

المعالجة المتعددة

ستحافظ على تنسيق الذاكرة "علامة"

كتلة تنسيقات الذاكرة

لا تعتمد واجهة برمجة التطبيقات أعلاه على الأبعاد / الخطوات / الأحجام ، مما يعني أنه يمكننا توسيع الوظائف في المستقبل مع الاحتفاظ بنفس واجهة برمجة التطبيقات.

واجهات برمجة التطبيقات الداخلية

سيتمكن المشغلون من التفرع بناءً على تنسيق الذاكرة

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

إذا قمنا بعمل memory_format مثل TensorOptions ، فيمكننا التفكير في التفرع على مستوى الإرسال (على غرار الجهاز والتخطيط)

جزء صغير من الملاحظات اقتراح VitalyFedyunin - أعتقد أنه يتطلب موترات 4D هنا

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

مقيد للغاية (لأننا نريد أيضًا التعامل مع 1D و 3D بالإضافة إلى 2D) ، و channels_first/channels_last من الاقتراح الأصلي كانت أكثر مرونة لهذا الغرض.

موافق ، نحن بحاجة إلى تسمية أفضل. channels_first صحيحًا تقريبًا باستثناء الدفعة الأولى =)

أنا أحب اقتراحك الأخير. هل سيتغير التعامل مع .contiguous ()؟ هل تحتاج إلى .contiguous (memory_format = <...>)؟ إذا كان الأمر كذلك ، والكثير من العمليات تستدعي ببساطة .contiguous () ، فلا يزال بإمكانهم تنسيق الذاكرة بشكل غير صحيح. تقوم العديد من العمليات اليوم أيضًا بتخصيص المخرجات على أنها blank_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)

هناك متغير آخر هو:

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) ، مما يعني أن الحجم (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(...)

لا تعجبني فكرة مدير السياق ، لأنها ستخفف من السيطرة على شكل الذاكرة.

على سبيل المثال:

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]

تضمين التغريدة

من وجهة نظر المستخدم ، فإن تسمية الطريقة (إذا بقيت على هذا النحو) تبدو مضللة بالنسبة لي لأن "إلى" هي بالفعل الطريقة الموصى بها لنقل Tensor إلى أجهزة مختلفة.

أيضا، ماذا عن شيء من هذا القبيل واحد نمباي ل لتحويل C_ORDER وF_ORDER المصفوفات؟

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 من المنطقي ، نحتاج إلى التأكد من إبلاغ المستخدمين النهائيين بهذا الأمر عند استقرار واجهة برمجة التطبيقات هذه.

dzhulgakovVitalyFedyunin وبعد استعراض # 19975، ولدي بعض مخاوف جديدة بشأن تسجيل العلامة شكل الذاكرة في الموترة. مشكلتي الأساسية هي ، كيف لنا أن نقرر ما إذا كانت العمليات يجب أن تحافظ على بطاقة الذاكرة؟ في الأصل ، كنت أعتقد أن المشغلين "المدركين للتخطيط البديل" فقط هم من يحتاجون إلى هذه الذكاء. لكن بالنظر إلى رقعة فيتالي ، أعتقد أن بعض المشغلين الأساسيين سيحتاجون أيضًا إلى التعديل أيضًا. على سبيل المثال ، ضع في اعتبارك x[0] ؛ إذا كان x سابقًا موتر NHWC ، فيجب أن أخرج موتر HWC بعد القيام بذلك. أنا متأكد تمامًا من أن تصحيح Vitaly لا يتعامل مع هذا بشكل صحيح ، وأراهن أن ذلك سيكون مربكًا جدًا للمستخدمين. ربما يكون المشغلون الوحيدون المتأثرون هم أولئك الذين يتخبطون بخطوات كبيرة (في هذه الحالة ، لا يوجد الكثير منهم ويمكننا تدقيقهم يدويًا) ، ولكن يبدو أنه شيء يجب علينا القيام به. ماذا تعتقد؟

انتظر ، لا تزال المُوتّرات مفهرسة بالترتيب: 0-dim N ؛ 1st خافت C ؛ 2-خافت H ؛ 3rd-dim W. لذا تُرجع x [0] موترًا بـ 0-خافت C ؛ 1st خافت H ؛ 2nd-dim W. بغض النظر عما إذا كان x هو تخطيط الذاكرة channels_first أو channels_last للذاكرة.

وإلا فإن memory_format لا معنى لها ونحتاج فقط إلى تبديل الموتر.

نقطتي هي أن علامة تنسيق الذاكرة لا يتم الاحتفاظ بها. إذا تم وضع علامة على موتر الإدخال channels_last ، يتم وضع علامة على موتر الإدخال الجديد any

cc @ zou3519 ، يذكرني منطق انتشار التخطيط هنا بالكثير من انتشار الأبعاد المسماة في عمل الموتر المحدد.

ما زلت أتابع هذا الاقتراح. لكن ezyang يمكننا تتبع منطق انتشار التخطيط من خلال نشر علامة (أو اسم) لكل بُعد وبعد ذلك سيكون مكافئًا لتسمية الموترات باستخدام اصطلاحات الاسم

سيكون من الرائع لو تمكنا من ترتيب منطق علامة الذاكرة ومنطق الموتر المسمى تمامًا ، حتى لو كان لدينا مساران منفصلان للتنفيذ في البداية.

المرحلة 1

يوسع وظائف وظيفتي موتر .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) أن يكون موتر الإدخال ثلاثي الأبعاد أو 4 د أو 5 د ؛ ويفشل على خلاف ذلك.

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

ملاحظة: بنهاية المرحلة ، سيحسب واحد x.is_contiguous(memory_format=torch.channels_last) حالة Tensor في كل مكالمة. سيتم تحديث هذه الوظيفة لاحقًا.

المرحلة الثانية

الحفاظ على تنسيق الذاكرة لعمليات محددة:

  1. يحافظ المشغلون الأحاديون على العناصر على تنسيق الذاكرة الأخير.

    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. تحافظ معاملات العناصر الثنائية ( add ، sub ، mul ، div ) على تنسيق الذاكرة 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. نتيجة عملية إعادة الشكل (وما شابهها) ، إذا كان الإخراج "آخر القنوات" مقروءًا

    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

ماذا عن عمليات مثل القط / الانقسام؟ سيكون من المفيد لهم الحفاظ على تنسيق الذاكرة.

ezyang - بخصوص الفهرسة أعتقد أننا يجب أن نتوقف في مكان ما. تخطيط الذاكرة المختلف ليس شفافًا تمامًا ويجب السماح لبعض العمليات بتجاهلها. أنا أزعم أنه يجب السماح لـ x[0] بمسح العلامة ، بما في ذلك x[0].unsqueeze(0)

كما ذكر Raghu ، يجب أن يحتفظ القط / الانقسام بالعلامة إذا كان ذلك ممكنًا على الرغم من أنه استخدام شائع جدًا. أعتقد أن القاعدة العامة يجب أن تكون أنه طالما أن التشغيل لا يغير الترتيب أو يعيد ترتيب المحور بشكل غريب ، فيجب علينا الاحتفاظ بالعلامة. إذا تغير الترتيب - كل الرهانات معطلة.

أوافق في بعض الحالات على أن نفقد العلامة. لكنني سأختلف بشأن x[0] . يبدو هذا بالنسبة لي طريقة شائعة جدًا للانتقال من NCHW إلى CHW .

بعد عدة محادثات حول مدى الخلط بين أن تحمل Tensors (أو لا تحمل) channel_last "علامة" ، قررنا المخاطرة بإدخال تغييرات فاصلة لـ bc وترقية آلية للتنسور في تنسيق channels_last.

ماذا يعني ذلك لواجهة برمجة التطبيقات:

أي موترات ثلاثية الأبعاد ، 4d ، 5d بخطوات كبيرة مثل N ، 1 ، H ، [W ، [D]] ستحصل تلقائيًا على تنسيق الذاكرة الأخير.

لإنجاحه ، سنتخذ احتياطات خاصة لضمان أن يكون للمشغلين على القنوات_الموترات الأخيرة التي تنتج القنوات_الموترات الأخيرة أداء مشابه على الأقل للمشغلين في الموترات المتجاورة.

في حالة السيناريو الأسوأ:
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

من تحويلات Slack ، بناءً على طلب

ناتاليا غيملشاين [2:19 م]
لذلك أعتبر أنه لن يكون هناك مفهوم للعلامة.

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?```

فيتالي فيديونين [8:23 ص]
سيكون z هو channels_last

فيتالي فيديونين [8:25 صباحًا]
إذا لم تكن x1 هي channels_last في أي من المتغيرات المقترحة (ما لم نغير وظيفة القطعة لا لإرجاع المشاهدات) ، لذا فإن الالتفاف سيحولها إلى تنسيق متجاور (قنوات_الأولى) ويعود متجاورًا أيضًا

فيتالي فيديونين [9:12 ص]
ngimel شكرًا لك على التعليقات ، أعتقد أنه يمكننا الخروج بتعريف أكثر جدوى

ناتاليا غيميلشين [9:36 ص]
رد على موضوع:
لذا يبدو أنها مشكلة ، أليس كذلك؟ يعد التقسيم عبر أبعاد القنوات أمرًا شائعًا نسبيًا ، على سبيل المثال في الشبكات الشبيهة بالبداية. لذا ، إذا كان الموتر عبارة عن قنوات مُقسمة أولاً ، فإن ناتج الالتفاف سيكون القنوات أولاً (وهو سلوك حدسي ، وعلى الأرجح ما يريده المستخدم) ، إذا كان الموتر عبارة عن قنوات مقسمة - أخيرًا ، سيكون ناتج الالتواء مرة أخرى قنوات أولاً؟

ناتاليا غيميلشين [9:39 ص]
رد على موضوع:
ولكن فقط بسبب سلوك الإضافة غير التبادلية وكون y الوسيطة الأولى والقنوات أخيرًا ، أليس كذلك؟ ماذا ستكون نتيجة x1+y ؟ هل لدينا قواعد انتشار التخطيط للعمليات الثنائية في مكان ما؟

فيتالي فيديونين [10:44 ص]
1) نعم ، إنها مشكلة سنحلها باقتراح بديل. سأقوم ببعض الاختبارات الآن وسأدونها هذا الأسبوع (في يوم أو يومين).
2) x1 + y - يجب أن تنتج أيضًا channels_last وإلا سيكون الأمر محيرًا ، ونعم ، سيكون لدينا قواعد نشر التخطيط مكتوبة.

أعتقد أن الملاحظة التي قدمتها لـ VitalyFedyunin عندما تحدثنا عن هذا شخصيًا (لكنني لا أعتقد أنني تذكرت كتابة هذا في أي مكان) ، هي أن هناك درجة من الحرية في الالتفاف ، وهي عندما يحدث ذلك حجة لا يتطابق تصميمها مع الذاكرة مع أي حجة تعرف كيفية تنفيذها بكفاءة ، فما الشكل الذي يجب أن تتواصل معه؟ لأسباب BC ، يلزم الاتصال بالقنوات أولاً ، لكننا اتخذنا قرارًا تعسفيًا هنا - يمكن القول إنه يمكنك التواصل مع القنوات أخيرًا أيضًا. ربما يجب أن يكون لدينا نوع من التبديل المحلي للخيط والذي يوضح ما هي الإعدادات الافتراضية؟

ولكن يبدو أن هناك الكثير من التفاصيل التي يجب توضيحها ، ولست متأكدًا مما إذا كانت ستنجح في النهاية.

لذا فإن ضبابية الالتواء (والعوامل الأخرى المدركة للتخطيط ، فيما يتعلق بهذه المسألة ، على سبيل المثال ، اختزال العينات التي نظرت إليها مؤخرًا تبدأ باستدعاء .contiguous () على المدخلات - فما الذي من المفترض أن تعنيه؟) كان السبب الأساسي لتقديم العلامة ، iirc.

نعم ، أنا موافق على فتح تصميم العلامة مرة أخرى ، لكننا بعد ذلك
يجب أن تحل مشاكل كيفية نشر هذه العلامات بجدية ،
حتى عندما تفقد التنسيق (كما كان الحال مع التقسيم
على القنوات). أنا مغرم أكثر بصنع بعض "التخطيط الحالي"
نوع من مدير السياق ، من جعله يعتمد على البيانات.

مقتطفات من رسالة نجيميل لعام 2019-06-19 12:43:45 -0700:

لذا فإن ضبابية الالتواء (والعوامل الأخرى المدركة للتخطيط ، فيما يتعلق بهذه المسألة ، على سبيل المثال ، اختزال العينات التي نظرت إليها مؤخرًا تبدأ باستدعاء .contiguous () على المدخلات - فما الذي من المفترض أن تعنيه؟) كان السبب الأساسي لتقديم العلامة ، iirc.

راجع للشغل لماذا يتعين علينا إنشاء مفهوم جديد بدلاً من مجرد التمسك بـ layout ؟ لا أعتقد أن التمثيلات المتفرقة لها مفهوم محدد جيدًا للتنسيق مثل "channels_last" ، لذلك لا نحتاج إلى تمثيل منتج memory_formats * layouts (يشير layouts إلى الاستخدام الحالي ) ، ولكن فقط memory_format + layouts يعني أنه من الجيد استخدام نفس الوسيطة كما اعتدنا؟ بالنسبة لي ، فهي أقصر وأجمل وستسمح لنا بتجنب توسيع نطاق توقيعات المصانع إلى آلاف الحجج.

تم النظر في خيار التنسيق (راجع الملحق) ، لكننا وجدنا أنه سيؤدي إلى الكثير من تكرار الكود بالإضافة إلى عدم السماح بالتحويل التلقائي للموترات إلى تنسيق memory_format مختلف أثناء الطيران

بعد كل شيء ، شكل memory_format هو وسيلة لتخطي الموتر واختيار النوى والمخرجات المحسنة بسهولة والتي هي خاصية موتر متقدم ، وليست فئة مختلفة تمامًا

إلى حد ما ، تعد التخطيطات المتفرقة أيضًا طريقة لاختيار حبات محسّنة بسهولة للمصفوفات التي تكون في الغالب صفرية هل يمكنك توضيح جزء "بالإضافة إلى عدم السماح بتحويل الموترات تلقائيًا إلى تنسيق memory_format مختلف أثناء الطيران" من فضلك؟

قد يكون هذا سؤالًا ساذجًا ، ولكن لماذا تفكر PyTorch في واجهة برمجة التطبيقات هذه مقابل مجرد تعريض خيار لاستخدام NHWC في العمليات نفسها ، والتي من شأنها استدعاء نواة CuDNN الأساسية مباشرةً عند توفرها؟

يبدو أنه بالنسبة لحالة الاستخدام الشائع (خلط عمليات الصور مثل التحويل والتجميع باستخدام بنيات LM) سيكون هذا حلاً سهلاً. كمطور ، كل ما أريده هو Conv2d(..., nhwc=True) . هل هناك سبب يجعل هذا غير منطقي؟

لقد درسنا الأسلوب المماثل (إضافة خيار إلى المشغلين بدلاً من اشتقاق النواة من striding) ، ووجدنا صعوبة في تطبيقه للأسباب التالية:

  • سيتطلب هذا النهج من النواة القيام بإعادة تقييد الموتر المجاور لتطبيق NHWC kernel.
  • سيتعين على العامل التالي إعادة إدخال الإدخال مرة أخرى (ليصبح مجاورًا) ما لم يكن لديه أيضًا خيار nhwc=True .
  • للحصول على NHWC عبر الشبكة ، سيحتاج كل مشغل فردي إلى خيار nhwc=True .

ملاحظة. إذا كنت قلقًا بشأن وظائف CudNN Ex ، فنحن نتطلع إلى الكشف عن المشغلين المشابهين لـ cudnn_batch_norm_nhwc .

مرحبًا VitalyFedyunin ، رأينا أن الموتر المسمى مدعومًا في PyTorch 1.3. هل يمكن أن يحل ذلك (أو يحل جزئيًا) المخاوف بشأن دعم تنسيق NHWC (أو حتى حظره)؟ هل هناك أي خطة للمضي قدمًا في حالة NHWC بناءً على موتر مسمى؟

نحن نمضي قدمًا في دعم القنوات الأخير ، وسوف أنشر خارطة الطريق هذا الأسبوع هنا وفي قنوات Slack. نحن لا نفكر في إضافة التنسيقات المحظورة في أي وقت قريب (حيث سيتطلب ذلك إعادة كتابة جميع المشغلين).

شكرا. سيكون ذلك جيدًا!

تنفيذ المهام والتقدم داخل https://github.com/pytorch/pytorch/issues/28619

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

bartvm picture bartvm  ·  3تعليقات

a1363901216 picture a1363901216  ·  3تعليقات

Coderx7 picture Coderx7  ·  3تعليقات

ikostrikov picture ikostrikov  ·  3تعليقات

negrinho picture negrinho  ·  3تعليقات