Почему распределенная игрушка-пример TensorFlow занимает слишком много времени?

Я попытался запустить игрушечный пример для умножения и сложения матриц с использованием распределенного TensorFlow.

Моя цель состояла в том, чтобы вычислить (A^n + B^n), где A[,] и B[,] — матрицы LxL.

Я использовал 2 машины в общедоступном облаке, чтобы вычислить A^n на одной машине и B^n на второй машине, а затем снова сложить на первой машине.

Когда на машинах был только процессор - мой скрипт работал отлично.
Когда на обоих был GPU - он не запускался за разумное время! У него была огромная задержка...

Мой вопрос: что я сделал не так в своем скрипте?

Обратите внимание, что для машины 2 (task:1) я использовал server.join(), а машину 1 (task:0) использовал в качестве клиента на этом внутреннем графике.

#------------------------------------------------------------------
from zmq import Stopwatch; aClk_E2E = Stopwatch(); aClk_E2E.start()
#------------------------------------------------------------------
from __future__ import print_function
import numpy as np
import tensorflow as tf
import datetime

IP_1 = '10.132.0.2';     port_1 = '2222'
IP_2 = '10.132.0.3';     port_2 = '2222'

cluster = tf.train.ClusterSpec( { "local": [ IP_1 + ":" + port_1,
                                             IP_2 + ":" + port_2
                                             ],
                                   }
                                )
server = tf.train.Server( cluster,
                          job_name   = "local",
                          task_index = 0
                          )
# server.join() # @machine2 ( task:1 )

n =    5
L = 1000  

def matpow( M, n ):
    if n < 1:                 # Abstract cases where n < 1
        return M
    else:
        return tf.matmul( M, matpow( M, n - 1 ) )

G = tf.Graph()

with G.as_default():
     with tf.device( "/job:local/task:1/cpu:0" ):
          c1 = []
          tB = tf.placeholder( tf.float32, [L, L] )     # tensor B placeholder
          with tf.device( "/job:local/task:1/gpu:0" ):
               c1.append( matpow( tB, n ) )

     with tf.device( "/job:local/task:0/cpu:0" ):
          c2 = []
          tA = tf.placeholder( tf.float32, [L, L] )     # tensor A placeholder
          with tf.device( "/job:local/task:0/gpu:0" ):
               c2.append( matpow( tA, n ) )
          sum2 = tf.add_n( c1 + c2 )
#---------------------------------------------------------<SECTION-UNDER-TEST>
t1_2 = datetime.datetime.now()
with tf.Session( "grpc://" + IP_1 + ":" + port_1, graph = G ) as sess:
     A = np.random.rand( L, L ).astype( 'float32' )
     B = np.random.rand( L, L ).astype( 'float32' )
     sess.run( sum2, { tA: A, tB: B, } )
t2_2 = datetime.datetime.now()
#---------------------------------------------------------<SECTION-UNDER-TEST>

#------------------------------------------------------------------
_ = aClk_E2E.stop()
#------------------------------------------------------------------
print( "Distributed Computation time: " + str(t2_2 - t1_2))
print( "Distributed Experiment  took: {0: > 16d} [us] End-2-End.".format( _ ) )

person conflux    schedule 16.08.2017    source источник
comment
Не могли бы вы обновить свой пост с количественным утверждением, что, как ожидается, будет {сверхнизкой, адекватной-достаточно, слишком большой}-задержкой и что ожидается {быстрой, адекватно-достаточно, слишком медленно}-обработкой для явно заданные размеры матрицы 1E+{3,6,9} x 1E+{3,6,9}? Это справедливо и научно строго утверждать, не так ли?   -  person user3666197    schedule 17.08.2017
comment
Я считаю, что решил это, перейдя с машин Windows на машины Ubuntu. Вот цифры: на машинах с Windows при L=1000 это заняло около 15 секунд, а на Linux это заняло менее 1 секунды. sny идея, почему это не работает с окнами??   -  person conflux    schedule 18.08.2017
comment
@conflux: первый вызов может быть медленным из-за инициализации один раз для процесса, можете ли вы это исключить? Вы также можете вставить узлы tf.Print, чтобы получить отдельные временные метки и посмотреть, где и на каком этапе происходит замедление. Официальная версия в 15 раз медленнее на машине с Windows звучит как ошибка   -  person Yaroslav Bulatov    schedule 05.09.2017
comment
Кроме того, эта медлительность может быть объяснена этим коммуникационным узким местом, которое должно было быть исправлено. совсем недавно. Вы можете запустить игрушечные тесты в упомянутом потоке, чтобы увидеть, нет ли исправления в сборке Windows.   -  person Yaroslav Bulatov    schedule 05.09.2017


Ответы (1)


Распределенные вычисления — наша новая Вселенная или набор параллельных

Первый шаг в этой области всегда сложен. Потеря уверенности, которая воспринималась как должное в предыдущем опыте монолитных вычислений, множество новых проблем, которые не имели подобных проблем в координации одноузловых процессов, множество новых сюрпризов от новых порядков величины распределенного времени выполнения и распределенной (координации, если не тупиковая и/или действующая блокировка) проблемы с блокировкой.

Спасибо за добавление некоторых количественных фактов ~ 15 секунд "слишком долго" для A[1000,1000];B[1000,1000];n=5 - пока все хорошо.


Не могли бы вы добавить предложенные выше изменения кода и повторно провести эксперимент на той же реальной инфраструктуре?

Это поможет остальной части этой начатой ​​работы ( WIP здесь ).

-- ЗАРАНЕЕ СПАСИБО за запуск + публикацию обновленных фактов.


Трудно продолжать ATM с количественно подтвержденными утверждениями, однако мое внутреннее подозрение заключается в следующем:

 def matpow( M, n ):
     return  M if ( n < 1 ) else tf.matmul( M, matpow( M, n - 1 ) )

Он использует рекурсию, которая может быть слишком глубокой для анализаторов кросс-компилятора/ассемблера GPU и заданных масштабов тензоров, SMX GPU, которые являются «быстрыми» для микрокода с математически плотным ядром, SMX-локальные SM_registers (имеющие задержки около 22 GPU_CLKs (ну, может всего 8, если интеллектуальная оптимизация для извлечения из хорошо выровненной LRU-выровненной SM_L1-кэш-линии) должна будет перетекать в global_MEMORY (поскольку у каждого SM есть шанс для хранения менее 1 КБ в повторяющихся регистрах SM_Registers, наиболее дружественных к задержке доступа к памяти, но matmul() никогда не использует повторно ни одну ячейку матрицы, поэтому скрытие задержки никогда не будет оплачиваться меньше, чем каждый доступ к global_MEMORY + [PSPACE] шкалы .. ), где неожиданно возникают штрафы за задержку 600+ GPU_CLKs.

Хотя в этой озвученной анимации HPC matmul() упоминается типичная иерархия CPU/Lx-кэш/память , сообщение о том, почему любая обработка O(N^3) должна быть невероятно медленной на графических процессорах для того, чтобы N стало больше, чем может поместиться в SM_registers, тем не менее хорошо видно (и как вы это видите, представьте себе вся ваша дружественность к кешу потеряна в рекурсии matpow() ).

Ядра GPU дают наилучшие результаты для мелкомасштабных все еще-SM-local-convolutions (где именно эта SMX-локальность обеспечивает хорошее выравнивание [Data:SMX-local SM_REG] (и отсутствие необходимости кросс-SMX-коммуникации (что это не тот случай, когда обработка matmul() больше, чем просто ~ 7 x 7 матриц, которые могут поместиться на SM_REG-silicon, и все, что выше этого, должно начинать супер-умную гимнастику с выравниванием по трафарету, если стремление заплатить минимально необходимую сумму всего -GPU-local memIO-latencies (история идет вперед, если имеет место плохое host2dev/dev2host IO-и еще много мест, где исполнение-производительность бесконтрольно плохая)).

Затраты на задержку даже для одного matmul( A, A ) внезапно становятся чертовски большими по сравнению с типичными ядрами, специализирующимися на изображениях. (Конечно, существуют продвинутые методы, как обойти ограничения этого специализированного кремния, но даже если бы matmul() был мастером высокоуровневых блочно-матричных операций HPC, это будет не так, как naive-matmul() после того, как рекурсивные вызовы получат на сцене - это убьет даже умные трюки, так как нет [SPACE] для промежуточных значений "стека", за которые автоматически сгенерированный-ядерный-код будет платить огромными [TIME] штрафы.. даже для таких мелких масштабов, как 1000х1000).

   Category                     GPU
   |                            Hardware
   |                            Unit
   |                            |            Throughput
   |                            |            |               Execution
   |                            |            |               Latency
   |                            |            |               |                  PTX instructions                                                      Note 
   |____________________________|____________|_______________|__________________|_____________________________________________________________________|________________________________________________________________________________________________________________________
   Load_shared                  LSU          2               +  30              ld, ldu                                                               Note, .ss = .shared ; .vec and .type determine the size of load. Note also that we omit .cop since no cacheable in Ocelot
   Load_global                  LSU          2               + 600              ld, ldu, prefetch, prefetchu                                          Note, .ss = .global; .vec and .type determine the size of load. Note, Ocelot may not generate prefetch since no caches
   Load_local                   LSU          2               + 600              ld, ldu, prefetch, prefetchu                                          Note, .ss = .local; .vec and .type determine the size of load. Note, Ocelot may not generate prefetch since no caches
   Load_const                   LSU          2               + 600              ld, ldu                                                               Note, .ss = .const; .vec and .type determine the size of load
   Load_param                   LSU          2               +  30              ld, ldu                                                               Note, .ss = .param; .vec and .type determine the size of load
   |                            |                              
   Store_shared                 LSU          2               +  30              st                                                                    Note, .ss = .shared; .vec and .type determine the size of store
   Store_global                 LSU          2               + 600              st                                                                    Note, .ss = .global; .vec and .type determine the size of store
   Store_local                  LSU          2               + 600              st                                                                    Note, .ss = .local; .vec and .type determine the size of store
   Read_modify_write_shared     LSU          2               + 600              atom, red                                                             Note, .space = shared; .type determine the size
   Read_modify_write_global     LSU          2               + 600              atom, red                                                             Note, .space = global; .type determine the size
   |                            |                              
   Texture                      LSU          2               + 600              tex, txq, suld, sust, sured, suq
   |                            |                              
   Integer                      ALU          2               +  24              add, sub, add.cc, addc, sub.cc, subc, mul, mad, mul24, mad24, sad, div, rem, abs, neg, min, max, popc, clz, bfind, brev, bfe, bfi, prmt, mov
   |                            |                                                                                                                     Note, these integer inst. with type = { .u16, .u32, .u64, .s16, .s32, .s64 };
   |                            |                              
   Float_single                 ALU          2               +  24              testp, copysign, add, sub, mul, fma, mad, div, abs, neg, min, max     Note, these Float-single inst. with type = { .f32 };
   Float_double                 ALU          1               +  48              testp, copysign, add, sub, mul, fma, mad, div, abs, neg, min, max     Note, these Float-double inst. with type = { .f64 };
   Special_single               SFU          8               +  48              rcp, sqrt, rsqrt, sin, cos, lg2, ex2                                  Note, these special-single with type = { .f32 };
   Special_double               SFU          8               +  72              rcp, sqrt, rsqrt, sin, cos, lg2, ex2                                  Note, these special-double with type = { .f64 };
   |                                                           
   Logical                      ALU          2               +  24              and, or, xor, not, cnot, shl, shr
   Control                      ALU          2               +  24              bra, call, ret, exit
   |                                                           
   Synchronization              ALU          2               +  24              bar, member, vote
   Compare & Select             ALU          2               +  24              set, setp, selp, slct
   |                                                           
   Conversion                   ALU          2               +  24              Isspacep, cvta, cvt
   Miscellanies                 ALU          2               +  24              brkpt, pmevent, trap
   Video                        ALU          2               +  24              vadd, vsub, vabsdiff, vmin, vmax, vshl, vshr, vmad, vset
person user3666197    schedule 18.08.2017