fminunc : A VERY STRANGE PROBLEM!
Show older comments
Hi
I use fminunc to solve a minimization problem. Fminunc make hundeads of calls of a simple function which I optimized for the gpu to improve the performance.
This is what happens when I make a comparison between cpu and gpu for a single call (test is external to fminuc):
TVD=tvd_sim2_mex(x,y, lam, Nit,t); % 0.018 s
TVD=tvd_sim2(x,y, lam, Nit,t); % 0.003 s 6x
As you can see the performance is 6X faster for the cpu.
The gpu profiling tells me the problem is about gpu malloc.
And this is what happen when I call the function 1000 times:
for i=1:1000
tic
TVD=tvd_sim2_mex(x,y, lam, Nit,t);
mytime(i)=toc;
end % 0.0005 s 6x
TVD=tvd_sim2(x,y, lam, Nit,t); % 0.003 s
As you can see the performance is 6X faster for the gpu.
Now....I can't know what exaclty happens inside fminunc function but surly I can say without doubt that
the only difference between the two situation is the function tvd_sim2. No modification to fminunc has been made.
The gpu always delete the memory at the end of every single call.
The function tvd_sim2 is compiled only once before fminunc.
This is what happens when I make a comparison between fminunc using tvd_sim2 and tvd_sim2_mex
(the function tvd_sim launches fminunc):
tic
[y, cost] = tvd_sim(x, lam, Nit,t); % Run whit tvd_sim2
toc
Solver stopped prematurely.
fminunc stopped because it exceeded the iteration limit,
options.MaxIterations = 5.000000e+01.
Elapsed time is 48.020835 seconds.
and:
tic
[y, cost] = tvd_sim(x, lam, Nit,t); % Run with tvd_sim2_mex
toc
Solver stopped prematurely.
fminunc stopped because it exceeded the iteration limit,
options.MaxIterations = 5.000000e+01.
Elapsed time is 179.953791 seconds.
In few words.... why does it go slower even if it go faster?
I thought....in my "1000 times for loop" the variable y is always equal but fminunc changes it every time.
This is due to the optimization.
But this is a false problem:
for i=1:1000
y=rand(4096,1);
tic
TVD=tvd_sim2_MEX_mex(x,y, lam, Nit,t);
mytime(i)=toc;
end
disp('mean time:');
disp(mean(mytime));
mean time:
5.5624e-04
The fact is the gpu reallocate the memory every function call,there's no difference between an equal input or a different one!
I add the screenshots of the function's run with and without the gpu. As you can see all the time is in charge of this function.


Which environmental variable (in the broad sense) can so substantially modify the performance of a gpu running the same code?
Or beyond the evidence is it not the same code?
Thanks!
23 Comments
Walter Roberson
on 7 Nov 2022
I do not see a gather() in your gpu timing. timing is not valid without a gather()
Emiliano Rosso
on 7 Nov 2022
Edited: Emiliano Rosso
on 7 Nov 2022
Justin Hontz
on 8 Nov 2022
How large are the inputs you are passing to the MEX? (I.e. what are the values of mex1 and mex2?)
In the case the input sizes are small, I believe Matt J's answer is most relevant. The computation of the MEX would be too small to justify performing the computation on GPU. The generated code (assuming R2020b) contains several kernel launches and cudaMemcpy synchronization calls, and executing these calls will only bottleneck the computation if the amount of computation performed on the GPU is not sufficiently large.
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 9 Nov 2022
Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
Edited: Bruno Luong
on 8 Nov 2022
"I can't profile in them."
Why you want to profile code outside the place where the discrepency is reported by the profiler: the time of tvd_sim2_mex vs tvd_sim2 ?
Furthermore the pfile times are reported globally as selftime, and you can see that the big part is the taken by vd_sim2_mex alone.
Bruno Luong
on 8 Nov 2022
@Justin Hontz "The generated code (assuming R2020b) contains several kernel launches and cudaMemcpy synchronization calls, and executing these calls will only bottleneck the computation if the amount of computation performed on the GPU is not sufficiently large."
But that should also happens in the for-loop test, no? But the for-loop clearly shows gpu-mex is faster. Only when i is called from fminunc where GPU is slower.
Emiliano Rosso
on 9 Nov 2022
Something I don't understand: in the screenshit the execution times is 31s and 168s respectively for cpu and mex-gpu with 307275 function calls. That makes in average time of each function call of
avggputime = 168.3/307275
avgcputime = 31.022/307275
The gpu time is then compatible avec what you measure with the for-loop of 1000 calls;
for i=1:1000
tic
TVD=tvd_sim2_mex(x,y, lam, Nit,t);
mytime(i)=toc;
end % 0.0005 s 6x
TVD=tvd_sim2(x,y, lam, Nit,t); % 0.003 s
Just the cpu suddenly is faster x 30 on FMINUNC (from 3 to 0.1ms), But NOT that the GPU get slower by any stretch (about 0.5 ms).
Do I understand correctly or do I miss something?
You should post exact code of various timeing; etc.... not a snips do we can verify what you are doing.
Emiliano Rosso
on 9 Nov 2022
Edited: Emiliano Rosso
on 9 Nov 2022
Bruno Luong
on 9 Nov 2022
"but really gpu performance is naturally slower than cpu?"
We and Matt (who is the first of us) have told you that the GPU need to transfert data back and forth and that is not negligible to the arithmetics operations. So there is not surprise.
And think this thread can be close, no more mystery as far as I'm concerned.
We and Matt (who is the first of us) have told you that the GPU need to transfert data back and forth and that is not negligible
However, you might be able to achieve better efficiency if you rewrite your GPU mex so that instead of exchaging input/output with the CPU, it leaves it on the GPU, returning output and accepting input in the form of gpuArrays. This could make it so that the whole iterative loop of the optimization is executed on the GPU, avoiding CPU/GPU transfers.
You would need to find an optimization solver that supports gpuArray input. Optimization Toolbox solvers like fminunc do not, but fminsearch does and there might be some 3rd party unconstrained solvers on the File Exchange. Be mindful, of course, that fminsearch only works well for problems with a small number of unknown variables (<=6).
Incidentally, the fact that Optimization Toolbox solvers do not support GPU data types is something I've lamented and expressed to the MathWorks staff:
Emiliano Rosso
on 9 Nov 2022
Edited: Emiliano Rosso
on 9 Nov 2022
do you mean that I have to find a version of fminunc (or another with the same performance) that performs the operations external to tvd_sim2_mex (such as the gradient calculation) without ever translating the intermediate results but leaving them
@Emiliano Rosso To be clear, fminunc will not translate gpuArray results into CPU results. It will throw an error unless your tvd_sim2_mex delivers a CPU double array output. You have to find an alternative optimization routine that will not throw such an error if your mex were to leave its results on the GPU as a gpuArray. There is no real reason why fminunc should require this, that I can see. All the "external operations" that fminunc does are simple matrix algebra operations that gpuArray should already support.
Bruno Luong
on 9 Nov 2022
That would be a serious candidate for enhancement request. Of course it should extend to fminon, linprog, quadprog, lsqnonlin, etc...
Matt J
on 9 Nov 2022
In the thread I linked to above @Joss Knight said he had "captured" my comments, so I assume that to be as good as an enhancement request. That was 2018, however, and I have not heard of any follow up to it at MAB meetings and such.
does the unknown variables count must be 4096*2 +3 = 8195 ? Is this the reason why I can't use fminsearch for my problem?
@Emiliano Rosso Yes, that's too big. Maybe you can find a nonlinear conjugate gradient solver on the File Exchange or GitHub which uses only M-Coded matrix operations.
Bruno Luong
on 9 Nov 2022
Edited: Bruno Luong
on 9 Nov 2022
@Emiliano Rosso you should think about providing the gradient to fminunc, your model look simple enough to do it without much trouble. It will accelerate significantly regardless CPU/ or GPU implementation of obhective function.
Joss Knight
on 9 Nov 2022
Thanks Matt. I've refreshed their collective memories on the discussion in Optim. However, you should definitely bring this sort of thing up at appropriate MAB sessions.
Emiliano Rosso
on 9 Nov 2022
Edited: Emiliano Rosso
on 9 Nov 2022
Bruno Luong
on 9 Nov 2022
Edited: Bruno Luong
on 9 Nov 2022
If you don't provide the gradient MATLAB calls 4000-8000 time the objective to compute the gradients, if you do, then MATLAB do not need to evalutae 4000 time to estimate the gradient but get it from your function. Imagine the time you could save.
Emiliano Rosso
on 9 Nov 2022
Accepted Answer
More Answers (2)
Ram Kokku
on 8 Nov 2022
Edited: Walter Roberson
on 8 Nov 2022
0 votes
As my colleague Hariprasad mentioned, GPU Coder is a capable of
- Allocate memory once and reuse it for subsequent calls. Use cfg.GpuConfig.EnableMemoryManager = true; to enable this.
- Take MATLAB gpuArray as input. You are doing this already. But this may not always help. for example, if GPU Coder choices to keep the first use a particular input on CPU (for some reason), it would incur an additional copy.
Further,
- you may use gpucoder.profile ( https://www.mathworks.com/help/gpucoder/ref/gpucoder.profile.html ) to find the bottlenecks.
- Use of cell arrays and structures may not play will with GPU Coder with regards to copies. consider break the cell array elements to separate variables.
- Take a look at the generated code and see GPU Coder is able to parallelize the key piece of your code.
- If you are open to share your code, I can take a quick look.
5 Comments
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
I notice you compute the same expression twice
double(abs(y)-t>0).*y./t
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
Edited: Bruno Luong
on 9 Nov 2022
sum(abs((diffyx./ycut.^2).^2))
abs has no effect.
Both explicit casting to double is not necessary but it probably does make any harm either.
To summarize I would rather code like this (Warning: not tested)
ycut=(abs(y)-(t>0)).*y./t; % edit missing parenthesis
ycut=ycut+~ycut;
diffxx=x(2:n,1)-x(1:n-1,1);
diffxx=diffxx(2:n-1,1)-diffxx(1:n-2,1);
TVD=1/2.*sum(((y-x)./ycut.^2).^2) + lam.*sum(abs(diffxx));
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
Edited: Bruno Luong
on 8 Nov 2022
0 votes
Just shooting in the dark here and wonder if you let the UseParallel option of fminunc to true or false? It could be that the gradient computation is efficient on CPU but not on GPU depending on this option.
Also your objective function is not very differentiable with all the logical and abs, it could be that fminunc have the hard time to optimize, and time is more sentitive to the numerical truncation, that is differently with gpu-mex and cpu-matlab.
BTW the objective function is simple enough to compute analytic gradient.
11 Comments
Matt J
on 8 Nov 2022
Also your objective function is not very differentiable with all the logical and abs, it could be that fminunc have the hard time to optimize, and time is more sentitive to the numerical truncation, that is differently with gpu-mex and cpu-matlab.
I don't think so. The profiling results say the objective function was callled the exact same number of times in both cases, and there doesn't appear to be anything that would make the execution time per call dependent on the input arguments.
Bruno Luong
on 8 Nov 2022
Yes I agree. I wrote it before look at the screenshot more carefully.
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
Edited: Bruno Luong
on 8 Nov 2022
Why you said "I haven't grasped the essential problem yet"? I provided the theory of why the mex GPU code runs slower under fminunc despite the fact that is faster in the loop. Isn't that what you want to havve an explanation or do I miss something?
BTW have to profiled mex-cpu againts mex-gpu and matlab-cpu?
And when you are at it could you answer my previous question "do you let the UseParallel option of fminunc to true or false? It could be that the gradient computation is efficient on CPU but not on GPU depending on this option. "
Matt J
on 8 Nov 2022
@Emiliano Rosso Have you checked, though, that the results of the fminunc are the same for both implementations? Or at least, the same within reasonable floating point noise differences?
Emiliano Rosso
on 8 Nov 2022
Edited: Emiliano Rosso
on 8 Nov 2022
Bruno Luong
on 8 Nov 2022
@Emiliano Rosso thanks for the test, UseParallel is not the cuprit then.
Bruno Luong
on 8 Nov 2022
Edited: Bruno Luong
on 8 Nov 2022
"he most time spent is in tvd_sim2_mex, not in gradient calculation"
I think you still don't understand my theory (which obviously is not right)/
The gradient calculation calls tvd_sim2_mex sequentially or parallelly on top of the GPU parallelization depending on the option. My theory is that there could be some trafic jam if the GPU memory is limited, the data transfererd on the bus, or it is satured in term of maximum threads that the GPU can handle etc... This is different when you test in a for-loop since the function is carried out 1000 time but sequentially.
I don't believe you can looks at the time reported by the profiler and be sure that the function execution is slowed down without taking into account other activities is going on at the same time (in that case fminunc UseParallel option).
Emiliano Rosso
on 9 Nov 2022
Emiliano Rosso
on 9 Nov 2022
Edited: Emiliano Rosso
on 9 Nov 2022
Bruno Luong
on 9 Nov 2022
"if this can help"
Certainly I'll remember doing the wait the next tic-toc woth GPU code.
Categories
Find more on GPU Computing in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!