2017-07-28 2 views
0

Ich möchte die Leistung von Theano und CNTK auf eine sehr einfache Aufgabe vergleichen: Matrix-Vektor-Produkt auf der GPU. Ich verwende Theano 0.9.0 und CNTK 2.0.Benchmarking von Theano und CNTK mit einem einfachen Matrix-Vektor-Produkt auf der GPU

Ich möchte die Zeit, die für die Berechnung auf dem Gerät verbraucht wird, nur die Zeit für die Datenübertragung von Host zu Gerät oder umgekehrt messen.

Das Ergebnis, das ich bekam, war so etwas wie dieses: figure (timings theano vs cntk) (N ist die Anzahl der Wiederholungen D, die Größe der Matrix, auf 10000. gesetzt wurde.)

Frage 1:

Es scheint die Zeit, die für einige Vorbereitung verwendet wird (compiling the computational graph?) ist in der ersten Ausführung des Mat-Vec-Produkts in dem CNTK-Fall enthalten. Gibt es eine Möglichkeit, die Vorbereitung und die Ausführung in CNTK zu teilen, wie im Fall Theano?

Frage 2:

Ich bin zu Theano verwendet, aber völlig neu bei CNTK, so dass ich bin mir nicht ganz sicher, ob der CNTK Code zu dem Theano Code entspricht. Ich bin mir besonders nicht sicher, ob die Operation in der for-Schleife des CNTK-Codes tatsächlich im Gerät eingeschlossen ist, da prod.eval() ein numpy.darray zurückgibt. Fehle ich etwas?

-Code verwendet, um die Zeitpunkte zu messen:

import numpy as np 
import time 

# theano 
def test_matVecDot_theano(D, N): 
    import theano 
    import theano.tensor as T 
    A_cpu = np.random.normal(size=[D,D]).astype(np.float32) 
    x_cpu = np.random.normal(size=[D]).astype(np.float32) 
    A_gpu = theano.shared(A_cpu) 
    x_gpu = theano.shared(x_cpu) 
    b_gpu = theano.shared(x_cpu) 
    b_gpu_new = T.dot(A_gpu,x_gpu) 
    fnc = theano.function(inputs=[], outputs=None, updates=[(b_gpu, b_gpu_new)], allow_input_downcast=True) 
    tic = time.time() 
    for i in range(N): 
     fnc() 
    toc = time.time() 
    print("time_theano:",toc-tic) 

# cntk 
def test_matVecDot_CNTK(D, N): 
    import cntk as C 
    A_cpu = np.random.normal(size=[D,D]).astype(np.float32) 
    x_cpu = np.random.normal(size=[D,1]).astype(np.float32) 
    A_c = C.Parameter(init=A_cpu, dtype=np.float32) 
    x_c = C.Parameter(init=x_cpu, dtype=np.float32) 
    b_c = C.Parameter(init=x_cpu, dtype=np.float32) 
    prod = C.times(A_c, x_c) 
    tic = time.time() 
    for i in range(N): 
     b_c.value = prod.eval() # is this operation enclosed in the device? 
    toc = time.time() 
    print("time_cntk:",toc-tic) 
+0

Warum nicht 'nvprof' verwenden, um die Ausführungszeit des GPU-Kerns direkt zu messen? Obwohl ich bezweifle, dass es einen großen Unterschied geben wird, da sie einfach cuBLAS nennen. – Kh40tiK

Antwort

0

Die kurze Antwort nein ist, wird der Betrieb auf dem Gerät nicht eingeschlossen. Folgendes passiert: Wenn Sie eval() aufrufen, wird der Aufruf an C++ übergeben, der die Operation auf dem Gerät nach Möglichkeit ausführt. Wenn aus C++ herauskommt, überprüft CNTK, ob der Wert as_numpy Schlüsselwortargument ist, das standardmäßig True ist. Wenn as_numpy True ist, wird der GPU-Puffer eifrig in ein NumPy-Array kopiert.

Wenn Sie prod.eval aufrufen (as_numpy = False), konvertiert der Aufruf von eval den GPU-Puffer nicht in ein NumPy-Array. Wenn Sie das Ergebnis einer einfachen alten Variablen zuweisen, können Sie sehen, dass Sie ein CNTK-Wertobjekt erhalten. In Ihrem Code weisen Sie jedoch das Attribut .value von b_c zu. Diese Zuweisung wird von dem Setter der value Eigenschaft gehandhabt (da diese Antwort ein wenig zu technisch wird, schließe ich this link für andere Leser ein). CNTK führt diese Zuweisung auf dem Gerät aus, obwohl es schwer zu sagen ist. Dies ist, weil, wenn Sie versuchen, b_c.value zu untersuchen, wenn Sie die .value Eigenschaft Getter aufrufen, die Ihnen ein NumPy-Array geben wird. Es scheint also, dass das Ergebnis ein NumPy-Array ist, aber das ist nur eine Konsequenz der Verwendung von b_c.value. Jede andere Variable lässt Sie erkennen, dass es sich um ein CNTK-Value-Objekt handelt. Auch das gilt wieder, wenn Sie eval(as_numpy=False) tun.

Darüber hinaus verwendet CNTK Zeitstempel, so dass die obige Auswertung nur einmal auf der GPU passiert. Alle nachfolgenden N-1 Anrufe an eval() werden Sie nur geben den gleichen Wert Objekt (die Umstellung auf Numpy wird jedes Mal wenn passieren, es sei denn, Sie as_numpy=False angeben.

Schließlich erwarte ich nicht viele sinnvolle Lehren daraus lernen Benchmark: sowohl CNTK als auch Theano rufen die gleiche CuDNN-Implementierung auf, die Vorteile von CNTK liegen eher bei höheren Ebenen wie zB (a) kommt mit einer High-Level-Bibliothek (b) der Benutzer muss sich nicht um den Batch kümmern und Sequenzachsen außer einigen spezialisierten Operationen (c) effiziente rekurrente Netzwerke (d) effizientes i/o (e) einfaches verteiltes Training.

Und um Ihre Frage zu Setup-Zeit zu beantworten: Mein Verständnis ist, wenn Sie nur einmal die Funktion prüfen, die es kompilieren wird. CNTK hat tatsächlich zwei Arten von Kompilationen: Wenn Sie nur eval das erste Mal kompilieren wird der Vorwärtsdurchlauf. Wenn Sie später function.grad tun, wird es die Eval-Kompilation wegwerfen und erneut kompilieren, so dass es sowohl den Vorwärts- als auch den Rückwärts-Durchlauf bewältigen kann.