PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Anordnung von Pixelpipelines, oder warum suckt es 1 Pixel breite Polygone zu zeichen?


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.

ScottManDeath
2005-06-14, 00:03:54
Hier noch der ASM Code den der GLSL Compiler erzeugt


!!ARBfp1.0
OPTION NV_fragment_program2;
# cgc version 1.3.0001, build date May 25 2005 22:35:02
# command line args:
#vendor NVIDIA Corporation
#version 1.0.02
#profile fp40
#program main
#semantic neurons
#semantic input_vectors
#semantic cur_input
#var float4 gl_FragColor : $vout.COLOR : COL : -1 : 1
#var samplerRECT neurons : : texunit 0 : -1 : 1
#var samplerRECT input_vectors : : texunit 1 : -1 : 1
#var float cur_input : : c[1] : -1 : 1
#const c[0] = 0 64 1 0.5
PARAM c[2] = { { 0, 64, 1, 0.5 },
program.local[1] };
TEMP R0;
TEMP R1;
TEMP R2;
TEMP R3;
TEMP R4;
TEMP RC;
TEMP HC;
MOVR R2, c[0].x;
MOVR R0.x, c[0];
LOOP c[0].yxzw;
MOVR R3.x, R0;
ADDR R1.x, R3, c[0].w;
ADDR R4.x, R3, c[0].z;
MOVR R0.x, R1;
MOVR R0.y, c[0].x;
MOVR R1.y, c[1].x;
TEX R0, R0, texture[0], RECT;
TEX R1, R1, texture[1], RECT;
ADDR R3, R0, -R1;
ADDR R0.z, R4.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R1, R3, R3, R2;
MADR R2, R0, R0, R1;
ADDR R3.x, R4, c[0].z;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
ADDR R3.x, R3, c[0].z;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.z, R3.x, c[0].w;
MOVR R0.x, R0.z;
MOVR R0.y, c[1].x;
TEX R1, R0, texture[1], RECT;
MOVR R0.y, c[0].x;
MOVR R0.x, R0.z;
TEX R0, R0, texture[0], RECT;
ADDR R0, R0, -R1;
MADR R2, R0, R0, R2;
ADDR R0.x, R3, c[0].z;
ENDLOOP;
DP4R result.color, R2, c[0].z;
END
# 165 instructions, 5 R-regs, 0 H-regs

zeckensack
2005-06-14, 00:17:40
a)Versuch's mal mit einer "normalen" Texturkoordinate statt mit gl_FragCoord.y
Ich raffe auch irgendwie nicht ganz, wie der GLSL-Compiler das umgesetzt hat. Ich sehe da nur noch (undefinierte?) Konstanten :|
Jedenfalls habe ich (auf ATI-Hardware allerdings) schon die Erfahrung machen dürfen, dass dieser "Tweak" einen nicht ganz trivialen Shader um 250% beschleunigt hat.

b)Nimm 2D-Power-of-Two-Texturen wann immer du kannst. RECTs haben ein shice Prefetch-Verhalten und sollten nur dann genutzt werden wenn man sich die Speicherverschwendung wirklich nicht leisten kann. Außerdem kann nicht jede Architektur mal eben "for free" unnormierte Texturkoordinaten verarbyten. Bei NV3x weiß ich das es frei ist, und bei R3xx weiß ich dass es was kostet. Bei NV4x weiß ich garnichts :)

Coda
2005-06-14, 00:17:41
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: Ja das siehst du richtig, weil Polygone immer auf Quad- und nicht Pixelbasis gezeichnet werden.

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 denke mal dein Problem sind die vielen State Changes. Kannst du nicht versuchen mehr zu batchen?
Auch sind 1024 Werte geradezu lächerlich wenig um einen Vorteil von einem Streamprozessor ziehen zu können. Da must du schon ein paar Magnituden mehr reinwerfen ;)

ScottManDeath
2005-06-14, 00:29:48
a)Versuch's mal mit einer "normalen" Texturkoordinate statt mit gl_FragCoord.y
Ich raffe auch irgendwie nicht ganz, wie der GLSL-Compiler das umgesetzt hat. Ich sehe da nur noch (undefinierte?) Konstanten :|
Jedenfalls habe ich (auf ATI-Hardware allerdings) schon die Erfahrung machen dürfen, dass dieser "Tweak" einen nicht ganz trivialen Shader um 250% beschleunigt hat.

Ich hatte spassenshalber mal anstelle von gl_FragCoord 0.0 eingefügt, dann lief es ca mit 90% der Zeit, auch nicht überragend der Unterschied. Ich guck dann mal nach.

b)Nimm 2D-Power-of-Two-Texturen wann immer du kannst. RECTs haben ein shice Prefetch-Verhalten und sollten nur dann genutzt werden wenn man sich die Speicherverschwendung wirklich nicht leisten kann. Außerdem kann nicht jede Architektur mal eben "for free" unnormierte Texturkoordinaten verarbyten. Bei NV3x weiß ich das es frei ist, und bei R3xx weiß ich dass es was kostet. Bei NV4x weiß ich garnichts :)

Das Problem ist das die Daten die ich verrechnen soll nicht POT sind. Deswegen hab ich Rect genommen.

Wenn ich POT 2D nehmen würde, verkompliziert sich ja auch noch die Adressierung....

Dummweise hab ich nicht mehr viel Zeit, ich muss ja auch noch den Text der Arbeit scheiben :(

zeckensack
2005-06-14, 00:35:19
Hmmmm ...
Haben deine Quell-Texturen GL_TEXTURE_MIN_FILTER und GL_TEXTURE_MAG_FILTER jeweils =GL_NEAREST?

AA/AF aus? :D [/scherz]

ScottManDeath
2005-06-14, 00:37:53
Ja das siehst du richtig, weil Polygone immer auf Quad- und nicht Pixelbasis gezeichnet werden.

Dumm, naja dann muss ich mal sehen wie ich den Shader umstricke.

Ich denke mal dein Problem sind die vielen State Changes. Kannst du nicht versuchen mehr zu batchen?

Der Algo geht noch weiter: Wenn ich die Distanzen habe, suche ich mir das Minimum davon, d.h. den Index den Neurons was dem Eingabevektor am nächsten liegt. Dann muss ich das Neuron um eine gewisse Strecke auf den Eingabevektor hinbewege. Dazu muss ich leider Rendertarget Pingpong spielen. In den 3s von oben ist das schon mit drinne, auch das bewegen des Neurons.

Ich denke nicht dass ich wesentlich mehr batchen kann. Sicherlich kann ich hier und da noch etwas optimieren, aber im Großen und Ganzen wird das wohl mehr nicht werden :(

Auch sind 1024 Werte geradezu lächerlich wenig um einen Vorteil von einem Streamprozessor ziehen zu können. Da must du schon ein paar Magnituden mehr reinwerfen ;)

Ich hab zum testen recht kleine Werte genommen, da es so schon lang genug warten muss ;)

Die 1024 sind noch mal 4 zu nehmen, da ich 4 Komponenten in einen RGBA Werte Packe.

Irgendwann geht mir auch der Videospeicher aus..... deswegen sind das auch schon Grenzen.

ScottManDeath
2005-06-14, 00:39:54
Hmmmm ...
Haben deine Quell-Texturen GL_TEXTURE_MIN_FILTER und GL_TEXTURE_MAG_FILTER jeweils =GL_NEAREST?

AA/AF aus? :D [/scherz]

Jups, auch GL_CLAMP_TO_EDGE als wrap mode.

Demirug
2005-06-14, 14:52:11
Um mal auf die ursprüngliche Frage zurück zu kommen.

Deine Pixelpipelines arbeiten schon alle. Aber immer mit 2*2 Pixel Blöcken. Wenn dein Dreieck nur ein Pixel breit ist hast du schon mindestens 50% der Leistung verloren.

ScottManDeath
2005-06-14, 14:56:22
Um mal auf die ursprüngliche Frage zurück zu kommen.

Deine Pixelpipelines arbeiten schon alle. Aber immer mit 2*2 Pixel Blöcken. Wenn dein Dreieck nur ein Pixel breit ist hast du schon mindestens 50% der Leistung verloren.

Ich hatte testweise mal die gleiche Pixelanzahl, aber untschiedliche Breite/Höhe getestest. Dann war ich, wie zu erwarten doppelt so schnell. Dann werd ich wohl noch auf "richtige" rechtecke umstellen.

Demirug
2005-06-14, 15:08:15
Ich hatte testweise mal die gleiche Pixelanzahl, aber untschiedliche Breite/Höhe getestest. Dann war ich, wie zu erwarten doppelt so schnell. Dann werd ich wohl noch auf "richtige" rechtecke umstellen.

Es gibt da übrigens noch einen kleinen Trick der noch etwas Leistung rausholen kann. Man nimmt kein Quad über die fläche des Targets sondern ein Dereieck das über die Fläche hinausragt aber diese komplett abdeckt.

ScottManDeath
2005-06-14, 15:30:30
Wieviel kann das ca bringen?

Ist das desegen günstiger weil an der Diagonalen des Quads kein Verschnitt durch die Pixelpipelines auftritt?

ScottManDeath
2005-06-16, 02:55:35
Hab jetzt so ziemlich alles vorgeschlagene umgesetzt.


2D Texturen, POT
rendern von x*y Polygonen, allerdings ist jetzt meine Textureadressierungslogik kompliezierter
gl_FragCoord weggelassen, dafür jetzt Texturekoordinaten gesampelt
Insgesamt hat es nicht allzuviel gebracht: ich habe jetzt ca 70 - 90% der Laufzeit gegenüber meiner Ausgansvariante, bin damit immer noch ca doppelt so langsam wie mein sse Code. Mal sehen wie das ganze skaliert wenn ich die Problemdimensionen erhöhe.