ScottManDeath
2005-06-13, 23:57:55
Ich versuche gerade für meine Diplomarbeit neuronale Netze auf die GPU (6800GT) zu portieren. Dabei muss ich unter anderem die Distanzen von hochdimensionalen Vektoren bestimmen.
Ich habe die Neuronen in einer 32 Bit Float Textur, die Eingabevektoren in einer anderen.
Jeweils in einer Zeile steht ein Vektor. Ich wähle (auf der CPU) zufällig einen Eingabevektor (= Zeile der Eingabevektortextur) aus und bestimme die Distanzen von allen Neuronen (=Zeilen der Neuronentextur) zu diesem Eingabevektor.
Dann rendere ich eine Polygon ( als Quad [1 x Neuronenanzahl Pixel]) und berechne mit folgendem Shader die Distanz. Dabei iteriere ich im Shader durch die einzelnen Spalten der Texturen und verechne sie dann.
Dort wo %f steht, wird von meiner App der Shader Source angepasst. Grund ist dass der NV40 nur 255 Loop Iterationen zulässt, ich aber deutlich mehr brauche. Deshalb das partielle Loop Unrolling.
Außerdem kann ich es für so auch für geringe Iterationsanzahlen auch auf meiner GeForce FX 5600 Go Entwicklungsmaschine testen. =)
Ich gucke mal ob ich den Shader noch schlanker bekomme.
uniform samplerRect neurons;
uniform samplerRect input_vectors;
uniform float cur_input;
#define UNROLL_BY_16 %f
#define UNROLL_BY_8 %f
#define UNROLL_BY_4 %f
#define UNROLL_BY_2 %f
#define UNROLL_BY_1 %f
vec4 Evaluate(in float index)
{
float d = index + 0.5;
vec4 neuron_val = textureRect(neurons, vec2(d , gl_FragCoord.y));
vec4 input_val = textureRect (input_vectors, vec2(d , cur_input));
vec4 diff = neuron_val - input_val;
return diff * diff;
}
void main()
{
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
float cur_d = 0.0;
for( float i = 0.0; i < UNROLL_BY_16; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_8; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_4; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_2; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_1; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
}
gl_FragColor = vec4(dot( sum, vec4(1.0)));
}
Ich rendere das ganze 5000 mal, mache dabei häufiges Wechseln der Rendertargets. Mit einem NOP Shader brauche ich dafür ca 3 s.
Ich brauche für 512 Neuronen , 1024 dimensional, ca 56 s.:frown:
Der Witz ist dass ich wenn ich anstelle von 1x512 jedoch 32x16 Pixel (= 512 Pixel gesamt) rendere, ich nur die Hälfte der Zeit benötige (ca 23 s).
Ich vermute dass es daran liegt dass bei 1 pixel Breiten Polygonen nur 2 von 4 Pixelshaderpipelines arbeiten? :confused:
Wenn ich das Ganze mit SSE auf der CPU mache, brauche ich ca 28 s. Davon bin ich zur Zeit mit der GPU weit enfernt :(
Rein theoretisch müsste ich doch von der Bandbreite/arithmetischen Leistung der GPU profitieren?
Nachdem ich die Distanzen bestimmt habe, rechne ich noch etwas weiter, allerdings ist das nicht der Flaschenhals, sondern die Distanzfunktion ist es.
Ich habe die Neuronen in einer 32 Bit Float Textur, die Eingabevektoren in einer anderen.
Jeweils in einer Zeile steht ein Vektor. Ich wähle (auf der CPU) zufällig einen Eingabevektor (= Zeile der Eingabevektortextur) aus und bestimme die Distanzen von allen Neuronen (=Zeilen der Neuronentextur) zu diesem Eingabevektor.
Dann rendere ich eine Polygon ( als Quad [1 x Neuronenanzahl Pixel]) und berechne mit folgendem Shader die Distanz. Dabei iteriere ich im Shader durch die einzelnen Spalten der Texturen und verechne sie dann.
Dort wo %f steht, wird von meiner App der Shader Source angepasst. Grund ist dass der NV40 nur 255 Loop Iterationen zulässt, ich aber deutlich mehr brauche. Deshalb das partielle Loop Unrolling.
Außerdem kann ich es für so auch für geringe Iterationsanzahlen auch auf meiner GeForce FX 5600 Go Entwicklungsmaschine testen. =)
Ich gucke mal ob ich den Shader noch schlanker bekomme.
uniform samplerRect neurons;
uniform samplerRect input_vectors;
uniform float cur_input;
#define UNROLL_BY_16 %f
#define UNROLL_BY_8 %f
#define UNROLL_BY_4 %f
#define UNROLL_BY_2 %f
#define UNROLL_BY_1 %f
vec4 Evaluate(in float index)
{
float d = index + 0.5;
vec4 neuron_val = textureRect(neurons, vec2(d , gl_FragCoord.y));
vec4 input_val = textureRect (input_vectors, vec2(d , cur_input));
vec4 diff = neuron_val - input_val;
return diff * diff;
}
void main()
{
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
float cur_d = 0.0;
for( float i = 0.0; i < UNROLL_BY_16; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_8; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_4; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_2; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
sum += Evaluate(cur_d);
cur_d += 1.0;
}
for( float i = 0.0; i < UNROLL_BY_1; i += 1.0)
{
sum += Evaluate(cur_d);
cur_d += 1.0;
}
gl_FragColor = vec4(dot( sum, vec4(1.0)));
}
Ich rendere das ganze 5000 mal, mache dabei häufiges Wechseln der Rendertargets. Mit einem NOP Shader brauche ich dafür ca 3 s.
Ich brauche für 512 Neuronen , 1024 dimensional, ca 56 s.:frown:
Der Witz ist dass ich wenn ich anstelle von 1x512 jedoch 32x16 Pixel (= 512 Pixel gesamt) rendere, ich nur die Hälfte der Zeit benötige (ca 23 s).
Ich vermute dass es daran liegt dass bei 1 pixel Breiten Polygonen nur 2 von 4 Pixelshaderpipelines arbeiten? :confused:
Wenn ich das Ganze mit SSE auf der CPU mache, brauche ich ca 28 s. Davon bin ich zur Zeit mit der GPU weit enfernt :(
Rein theoretisch müsste ich doch von der Bandbreite/arithmetischen Leistung der GPU profitieren?
Nachdem ich die Distanzen bestimmt habe, rechne ich noch etwas weiter, allerdings ist das nicht der Flaschenhals, sondern die Distanzfunktion ist es.