Files
fold_slice/tomo/+tomo/block_fun.m
T
2026-08-07 15:56:42 +09:00

647 lines
29 KiB
Matlab
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
% BLOCK_FUN autosplitting function for fast GPU data processing.
% in the simplest case this function is equivalent to
% [varargout{:}] = fun(fp16.get(stack_object(ROI{:},:)), varargin{2:end});
% But for large datasets, it will split the stack_object along 3rd axis and try to also
% split other inputs if possible. Then it will submit each of the
% blocks on 1 or more GPUs and process. Processed data are returned back to RAM.
% it is useful even to process large blocks of data in RAM, to prevent too large RAM allocation.
% NOTE: Avoiding GPU processing ('use_gpu', false) can be useful for simple operations when the memory transfer to GPU is much slower than task execution !!
% NOTE: Multi GPU processing addes extra overhead for moving data to shared arrays and back, useful only for very expensive operations
% NOTE: If input is fp16 precision, it is automatically converted to singles for processing and result are converted back to fp16 when returned (unless ('use_fp16', false) is used)
% NOTE: Automatic splitting assumes memory required for two FFT operation and real to complex conversion with single precision numbers, ie MEMORY = 4*6*8*numel(stack_object)
%
% varargout = block_fun(fun, stack_object, varargin)
%
% Inputs:
% **fun - function to be executed
% **stack_object - 3D array that will be split along 3rd dimension and processed on GPU (as default)
% **varargin - other arguments, if their size along 1st dimension is the same as 3rd axis of stack_object, they will be split
% **parameter structure - if the last argument is structure,
% it will not be given to the "fun" but it will be used as a parameter input for block_fun.
% SEE DESCRIPTION IN CODE FOR ALL POSSIBLE INPUTS
% *returns*
% ++varargout - 3D array of size stack_object or 1D/2D with 1st dimension assumed to be equal to 3rd axis of stack_object
%
% Examples:
% % simple 1:1 array processing
% X = randn(Nx, Ny, Nz)
% X_fft = block_fun(@fft2,X); % equivalent to fft2(x) for small datasets
% % simple 1:1 array processing with manually defined blocks size , can be useful if autosplitting does not provide good results (ie too large / too small blocks)
% X_fft = block_fun(@fft2,X, struct('Nblocks', 10)); % equivalent to fft2(x) for small datasets
% % information reduction to (Nz,1,1),
% [x,y] = block_fun(@utils.center, X); % equivalent to utils.center(x) for small datasets
% % information reduction to (Nx, Nx,1)
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets
% % information reduction to (Nx, Nx,1) using only CPU
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('use_gpu', false, 'reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets but avoids using GPU
% % information reduction to (Nx, Nx,1) and return results as single even if inputs is fp16
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('use_fp16', false, 'reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets but avoids using GPU
% % use multiple inputs and perform inplace image shifting in order to prevent large memory allocation
% shift_x = randn(Nz,1); shift_y = randn(Nz,1);
% X_accum = block_fun(@utils.imshift_fft, X, shift_x, shift_y, struct('inplace', true)); % equivalent to imshift_fft(X, shift_x, shift_y)
% % process only a small subregion of input array, avoid slow matlab memory copy / allocation
% ROI = {1:10, 4:50};
% X_phase_cropped = block_fun( @angle, X, struct('ROI', ROI)); % angle(X(ROI{:}))
% % make the processing quiet, ie no progressbars are shown
% X_phase = block_fun( @angle, X, struct('verbose_level', 0)); % angle(X)
% clean all memory blocks:
% ! ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  Except where otherwise noted, this work is licensed under a          |
%|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            |
%|  International (CC BY-NC-SA 4.0) license.                             |
%|                                                                       |
%|  Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch)    |
%|                                                                       |
%|      Author: CXS group, PSI  |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
% computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a
% publication or if it is fully or partially rewritten for another
% computing language the authors and institution should be acknowledged
% in written form in the publication: “Data processing was carried out
% using the “cSAXS matlab package” developed by the CXS group,
% Paul Scherrer Institut, Switzerland.”
% Variations on the latter text can be incorporated upon discussion with
% the CXS group if needed to more specifically reflect the use of the package
% for the published work.
%
% A publication that focuses on describing features, or parameters, that
% are already existing in the code should be first discussed with the
% authors.
%
% This code and subroutines are part of a continuous development, they
% are provided “as they are” without guarantees or liability on part
% of PSI or the authors. It is the user responsibility to ensure its
% proper use and the correctness of the results.
function varargout = block_fun(fun, stack_object, varargin)
global pprev
assert(~isempty(stack_object), 'Processed array is empty')
Nlayers = size(stack_object,3);
varargout = cell(nargout,1);
varargin = [{stack_object}, varargin];
% check if there is given parameter structure
if isstruct(varargin{end})
param = varargin{end};
varargin = varargin(1:end-1); % !!! remove the parameter structure from the vargin list
else
param = struct();
end
ninputs = length(varargin);
assert(~isempty(stack_object), 'Input array is empty')
%% ============= LOAD PARAMETERS FROM PARAM STRUCTURE OR USE PREDEFINED DEFAULTS ================
if ~isfield(param, 'verbose_level')
param.verbose_level = 1;
end
if ~isfield(param, 'use_GPU')
param.use_GPU = true; % some operations are just faster without moving to GPU
end
if ~isfield(param, 'move_to_GPU')
param.move_to_GPU = true; % move blocks directly yo GPU or let the called function to decide
end
if ~isfield(param, 'GPU_list') || isempty(param.GPU_list)
if param.use_GPU && gpuDeviceCount > 0
gpu = gpuDevice;
param.GPU_list = gpu.Index;
else
param.GPU_list = -1; % list of the used GPUs
end
end
if ~isfield(param, 'inplace')
param.inplace = false; % run the function inplace , !! dont stop the operation in middle of process !!
% assume that output is the same as first
% input array and it will write results
% into this memory without reallocating
end
if ~isfield(param, 'ROI') || isempty(param.ROI) % process only limited ROI of all inputs of size stack_object
param.ROI = {':', ':'};
end
Nelements = 1;
for i = 1:2
if strcmpi(param.ROI{i},':')
param.ROI{i} = 1:size(stack_object,i);
end
Nelements = Nelements * length(param.ROI{i});
end
assert(Nelements>0, 'Selected ROI is empty')
if ~isfield(param, 'Nblocks') % number of blocks to split the input volume, [] = auto
param.Nblocks = [];
end
if ~isfield(param, 'full_block_size') % largest volume size in the calculation, default = size(stack_object)
param.full_block_size = size(stack_object);
end
if ~isfield(param, 'use_fp16')
param.use_fp16 = isa(stack_object, 'uint16');
end
% function applied on the computed data to reduce along 3rd axis
if ~isfield(param, 'reduce_fun')
param.reduce_fun = [];
else
assert(ismember(func2str(param.reduce_fun), {'min','max','plus'}))
end
if ~isfield(param, 'use_shared_memory')
param.use_shared_memory = false; % use shared memory for data exchange, it is used only if N_GPU > 1 or for debugging
end
if param.inplace
varargout{1} = stack_object;
end
if param.inplace
warning on
warning off backtrace
warning('Inplace data processing, DO NOT INTERRUPT')
warning on backtrace
end
if (gpuDeviceCount == 0 || isa(stack_object, 'gpuArray') || ismatrix(stack_object)) && ...
(isempty(param.Nblocks) || param.Nblocks == 1)
%% ================ EXECUTE FUN =============================
[varargout{:}] = fun(stack_object(param.ROI{:},:), varargin{2:end});
else
%% GPU or CPU splitting is required
N_GPU = max(1,length(param.GPU_list));
if param.use_GPU
gpu = gpuDevice;
if ~ismember(gpu.Index,param.GPU_list)
gpu = gpuDevice(param.GPU_list(1));
end
end
if isempty(param.Nblocks)
%% autoestimate block size
if param.use_GPU
%% empirical condition assuming FFT involved, may be too pesimistic
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Nblocks = ceil( (4*6*8* prod(param.full_block_size)) / gpu.AvailableMemory) ;
Nblocks = max(Nblocks, prod(param.full_block_size)/ double(intmax('int32')));
Nblocks = max(Nblocks, N_GPU);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
else
%% if CPU is used, split it to <20GB blocks
max_block_size = min(utils.check_available_memory*1e6, 20e9); %% work with 10GB blocks
Nblocks = ceil( (6*8* prod(param.full_block_size)) / max_block_size) ;
end
else
Nblocks = param.Nblocks;
end
% optimize size of the block to equally use all provided GPUs
N = ceil(Nlayers / Nblocks);
Nblocks = ceil(Nlayers / N / N_GPU) * N_GPU; % make it splitable to N_GPU
N = ceil(Nlayers / Nblocks);
% parse inputs and store infomations needed to split the inputs
splitable_along_3rd_axis = false(ninputs,1);
apply_ROI = false(ninputs,1);
for jj = 1:ninputs
splitable_along_3rd_axis(jj) = ndims(varargin{jj}) == 3 && (size(varargin{jj},3) == size(stack_object,3));
apply_ROI(jj) = all([size(varargin{jj},1),size(varargin{jj},2)] == [size(stack_object,1), size(stack_object,2)]) && ...
((size(varargin{jj},1) ~= length(param.ROI{1})) || (size(varargin{jj},2) ~= length(param.ROI{2})));
end
% expected inputs are singles or uint16, if they are doubles, convert to single so save memory and speed up execution on GPU
for jj = find(splitable_along_3rd_axis)'
if ~ismember(class(varargin{jj}), {'single', 'uint16', 'uint8'})
varargin{jj} = single(varargin{jj});
end
end
if Nblocks == 1
%% in case no splitting is needed
varargout = process_in_single_block(fun, varargin, apply_ROI,splitable_along_3rd_axis, nargout, param);
return
else
% when the function is finished, make sure to execute following code
global status
status = true;
% avoid using shared memory if not needed, it is a bit slower
use_shared_memory = length(param.GPU_list) > 1 || param.use_shared_memory;
if use_shared_memory
out = onCleanup(@myCleanupFun);
end
for ii = 1:length(varargin)
if splitable_along_3rd_axis(ii) || N_GPU > 1
varargin{ii} = gather(varargin{ii});
end
end
ind = {};
for ii = 1:Nblocks
ind{end+1} = 1+N*(ii-1) : min(Nlayers, N*ii);
% avoid single layer arrays
if length(ind{end}) < 2
ind{end-1} = [ind{end-1}, ind{end}];
ind(end) = [];
end
end
Nblocks = length(ind);
%% open parpool to allow multi GPU processing
if N_GPU > 1
poolobj = gcp('nocreate');
if isempty(poolobj) || poolobj.NumWorkers < N_GPU
delete(poolobj);
poolobj = parpool(N_GPU);
end
poolobj.IdleTimeout = 600; % set idle timeout to 10 hours
end
if param.verbose_level>0
pprev = -1;
end
%% START OF OUTER GPU LOOP
outputs_blocks = [];
%% unitialize one solver per GPU
for block_id = 1:N_GPU
% parse inputs and try to split them if possible
[outputs_blocks, inputs_block{block_id}] = submit_block(block_id,block_id, ind, varargin,outputs_blocks ,param, fun, apply_ROI, use_shared_memory, Nlayers,nargout, splitable_along_3rd_axis );
end
unprocessed_blocks = N_GPU+1:Nblocks;
%% merge blocks back from GPUs
for ii = 1:Nblocks
if param.verbose_level>0; utils.progressbar(ii, Nblocks); end
% set values from the small blocks to the final output arrays
[thread_id, varargout] = gather_block(varargout,param,Nlayers, nargout,ind, N, outputs_blocks);
if ~isempty(unprocessed_blocks)
block_id = unprocessed_blocks(1);
unprocessed_blocks(1) = [];
% submit a new job once the previous is finished
[outputs_blocks, inputs_block{thread_id}] = submit_block(block_id,thread_id,ind, varargin,outputs_blocks ,param, fun, apply_ROI, use_shared_memory, Nlayers,nargout, splitable_along_3rd_axis );
if isa(outputs_blocks, 'parallel.FevalFuture') && any(cat(1,[outputs_blocks.Read]))
outputs_blocks
warning on
warning('Unknown error in parallel processing')
keyboard
end
end
end
% END OF OUTER GPU LOOP
end
end
% everything was fine -> no cleaning needed
status = false;
if param.inplace
warning on
warning off backtrace
warning('Inplace data processing finished')
warning on backtrace
end
end
%% auxiliary function
function [outputs_blocks,inputs_block] = submit_block(block_id, thread_id, ind, inputs, outputs_blocks, param, fun, apply_ROI, use_shared_memory, Nlayers , Noutputs, splitable_along_3rd_axis )
inputs_block = inputs;
ind = ind{block_id};
% get blocks of data
for jj = 1:length(inputs)
if splitable_along_3rd_axis(jj)
if apply_ROI(jj)
ROI = param.ROI;
else
ROI = {};
end
% use fast splitting based on MEX files for large splitable blocks
inputs_block{jj} = get_block(inputs_block{jj}, ind, ROI,use_shared_memory);
elseif isnumeric(inputs_block{jj}) && size(inputs_block{jj},1) == Nlayers
% !! assume split along first dimension of the object !!!
inputs_block{jj} = inputs_block{jj}(ind,:,:,:); % split it and provide subfunction only the valid chunk
end
end
if isempty(outputs_blocks); clear outputs_blocks; end
%% ----------- call the function "fun" , execute on GPUs -----------------
if length(param.GPU_list) <= 1 || ~param.use_GPU
%% single GPU
[outputs_blocks.data,outputs_blocks.id]...
= worker(fun,Noutputs, use_shared_memory,param,block_id,1,inputs_block);
else
%% run asynchronous on multiple GPUs
outputs_blocks(thread_id) = parfeval(@worker,2,fun,Noutputs, use_shared_memory,param,block_id,thread_id,inputs_block);
end
end
function out = get_block(full_array, ind, ROI, use_shared_memory)
Nlayers = length(ind);
positions = zeros(Nlayers,2,'int32');
if ~isempty(ROI)
Npix_small = [length(ROI{1}),length(ROI{2}),length(ind)];
assert(all(Npix_small <= size(full_array)), 'Provided ROI is larger than input array')
positions = positions+int32([ROI{1}(1),ROI{2}(1)]-1);
else
Npix_small = [size(full_array,1),size(full_array,2),length(ind)];
end
% allocate in RAM array to load the block from "stack_object"
stack_object_block_in = zeros(Npix_small, 'like', full_array);
if use_shared_memory
s = shm(); % create sharemem object
if isa(full_array, 'single')
s.allocate(stack_object_block_in); % allocate SHM memory
% attach the shared memory
[s, stack_object_block_shm] = s.attach();
% === write data =====
% use self-made MEX OMP function to move the data
utils.get_from_3D_projection(stack_object_block_shm, full_array, positions, ind);
elseif isa(full_array, 'uint16')
stack_object_block_in = zeros(Npix_small, 'like', full_array);
% === write data =====
% use self-made MEX OMP function to move the data
utils.get_from_3D_projection(stack_object_block_in, full_array, positions, ind);
% convert from fp16 to singles
stack_object_block_in = fp16.get(stack_object_block_in);
% upload to the shared memory
s.upload(stack_object_block_in); % seems to be really slow ????
%s.allocate(stack_object_block_in); % allocate SHM memory
%[s, stack_object_block_shm] = s.attach();
% % move to shared memory , a bit faster than the memcpy in the
% % sharedmem function
%utils.get_from_3D_projection(stack_object_block_shm, stack_object_block_in, zeros(Nlayers,2,'int32'), int32(1:Nlayers));
%toc
end
% detach the shared memory
s.detach;
% return structure with shared mem object
out = s;
else
% use custom made MEX OMP function to move the data
stack_object_block_in = utils.get_from_3D_projection(stack_object_block_in, full_array, positions, ind);
% if needed, convert from fp16 to singles
out = fp16.get(stack_object_block_in);
end
end
function myCleanupFun()
global status
if status
warning('!! cleaning all shared memory !!')
% destroy all shared memory that could have been left behind
!ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
end
end
function [thread_id, outputs] = gather_block(outputs,param,Nlayers,Noutputs, ind_all, Nl_per_block, output_package)
if isa(output_package, 'parallel.FevalFuture')
% gather results from cluster , WAIT FOR CALCULATIONS TO BE FINISHED
% [~, outputs_block, id] = fetchNext(output_package);
%% my version of the fetchNext function, it seems faster
id = [];
while true
for thread_id =1:length(output_package)
if strcmpi(output_package(thread_id).State, 'finished') && output_package(thread_id).Read == 0
try
[outputs_block, id] = output_package(thread_id).fetchOutputs;
catch err
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if ~isempty(output_package(thread_id).Diary)
fprintf('============ THREAD %i FAILED, OUTPUT: ============= \n', thread_id)
disp(output_package(thread_id).Diary)
end
fprintf('============ THREAD %i FAILED, ERROR: ============= \n', thread_id)
disp(getReport(output_package(thread_id).Error))
keyboard
output_package.cancel
rethrow(err)
end
break
end
end
if ~isempty(id); break; end
pause(0.01) % wait for the data to be prepared
end
else
thread_id = 1;
id = output_package.id;
outputs_block = output_package.data;
end
ind = ind_all{id};
for jj = 1:Noutputs
% get the results from GPU to RAM
% allocate RAM for the final output arrays , allocate when
% with the same complexity as the outputs_block{ii}{jj} has
s = shm();
if isa(outputs_block{jj}, 'shm')
% first download from the shared mem back to workspace
[s, outputs_block{jj}] = outputs_block{jj}.attach;
s.protected = false;
end
% processing returns the same number of layers as input
is_block = ndims(outputs_block{jj}) == 3 && size(outputs_block{jj},3) == length(ind) ;
% processing returns one layer per input
is_reduced = ismatrix(outputs_block{jj}) && size(outputs_block{jj},3) == 1 && size(outputs_block{jj},1) ~= length(ind) && ~isempty(param.reduce_fun) && jj ==1;
if isa(outputs_block{jj}, 'double')
% small arrays / scalars just convert
outputs_block{jj} = single(outputs_block{jj});
% for large complain
if is_block
warning('Output of the function should be blocks in single precision')
end
end
if is_block && param.use_fp16 && isa(outputs_block{jj},'single')
% move to fp16 precision to avoid large memory allocation
outputs_block{jj} = fp16.set(outputs_block{jj});
end
% if the first iteration when it is empty create the large output arrays
if isempty(outputs{jj})
try
if is_block
% return large array of size stack_object
Npix_small = [size(outputs_block{jj},1),size(outputs_block{jj},2),Nlayers];
outputs{jj} = zeros(Npix_small, 'like', outputs_block{jj});
elseif is_reduced
outputs{jj} = [];
elseif size(outputs_block{jj},1) == length(ind) && ndims(outputs_block{jj}) <= 3
% return smaller array split along first dimension
outputs{jj} = zeros([Nlayers, size(outputs_block{jj},2), size(outputs_block{jj},3)], 'like', outputs_block{jj});
else
error('Unimplemented option')
end
catch err
utils.check_available_memory
rethrow(err)
end
end
if is_block
% if the output is 3D array, add the results into the large output stored in RAM
outputs{jj} = tomo.set_to_array(outputs{jj}, outputs_block{jj}, Nl_per_block*(id-1), false);
elseif is_reduced
if isempty(outputs{jj})
outputs{jj} = outputs_block{jj};
else
outputs{jj} = param.reduce_fun(outputs{jj}, outputs_block{jj});
end
else
outputs{jj}(ind,:,:) = outputs_block{jj};
end
end
end
function [outputs,id] = worker(fun, Noutputs, use_sharemem, param, id, thread_id, inputs)
% let parfor to choose which GPU use
gpu_id = param.GPU_list(thread_id);
gpu = gpuDevice();
if ~isempty(param.GPU_list) && gpu.Index ~= gpu_id && gpu_id > 0
gpuDevice(gpu_id); % avoid unneeded initalization
end
if ~isempty(getCurrentTask())
% report only if inside parfor
utils.check_available_memory
if gpu_id > 0
fprintf('GPU %i ===== Memory %3.2GB/%3.2gGB \n',gpu.Index, gpu.AvailableMemory/1e9, gpu.TotalMemory/1e9 )
end
end
%% upload data on GPU
for jj = 1:length(inputs)
if isa(inputs{jj}, 'shm')
% data are downloaded from shared memory
[s,inputs{jj}]=inputs{jj}.attach;
end
if isnumeric(inputs{jj}) && numel(inputs{jj}) > 1e4 && param.use_GPU && param.move_to_GPU
inputs{jj} = gpuArray(inputs{jj});
end
end
outputs = cell(Noutputs,1);
Nlayers = size(inputs{1},3);
try
%%%%%% CALL THE WORKING FUNCTION WITH ARRAYS ALREADY MOVED TO GPU %%%%
[outputs{:}] = fun(inputs{:});
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~isempty(param.reduce_fun)
% apply additional function to reduce the data
switch func2str(param.reduce_fun)
case 'plus',outputs{1} = sum(outputs{1},3);
case 'min', outputs{1} = min(outputs{1},[],3);
case 'max', outputs{1} = max(outputs{1},[],3);
end
end
catch err
if any(strcmpi(err.identifier, {'parallel:gpu:array:OOMForOperation', 'parallel:gpu:array:OOM'}))
[~,out] = system(sprintf('nvidia-smi --id=%i | head -n10 | tail -n7', gpu.Index-1));
% disp(getReport(err))
disp(out)
warning('Low memory on GPU %i, RESETTING GPU ... \n', gpu.Index)
for jj = 1:length(inputs)
inputs{jj} = gather(inputs{jj});
end
% try to clean some leftover data from previous calculations
reset(gpuDevice)
fprintf('TRYING TO RECURSIVELLY CALL BLOCKFUN %s WITH 2 SUBBLOCKS \n', func2str(fun))
[outputs{:}] = tomo.block_fun(fun,inputs{:},struct('Nblocks',2));
else
rethrow(err)
end
end
for jj = 1:length(outputs)
%% return data from GPU
if isa(outputs{jj}, 'gpuArray') && param.use_GPU
outputs{jj} = gather(outputs{jj});
end
%% move data to shared memory if needed
is_block = ndims(outputs{jj}) == 3 && (size(outputs{jj},3) == Nlayers);
if is_block && use_sharemem
% data are distributed to shared memory
s = shm(true);
s.upload(outputs{jj})
outputs{jj} = s;
end
end
end
function outputs = process_in_single_block(fun, inputs,apply_ROI, splitable_along_3rd_axis, Noutputs, param)
for jj = 1:length(inputs)
if splitable_along_3rd_axis(jj)
% if stored as fp16 precision, convert to singles
inputs{jj} = fp16.get( inputs{jj});
% if provided and possible, use param.ROI to crop the working / inputs array
if apply_ROI(jj)
inputs{jj} = inputs{jj}(param.ROI{:},:);
end
end
if isa(inputs{jj}, 'double')
inputs{jj} = single(inputs{jj});
end
if param.use_GPU && isnumeric(inputs{jj}) && numel(inputs{jj}) > 1e2 && param.move_to_GPU
% upload on GPU
inputs{jj} = gpuArray(inputs{jj});
end
end
outputs = cell(Noutputs,1);
% execute the worker
[outputs{:}] = fun(inputs{:});
if ~isempty(param.reduce_fun)
% apply additional function to reduce the data
switch func2str(param.reduce_fun)
case 'plus',outputs{1} = sum(outputs{1},3);
case 'min', outputs{1} = min(outputs{1},[],3);
case 'max', outputs{1} = max(outputs{1},[],3);
end
end
for jj = 1:Noutputs
if param.use_GPU && isa(outputs{jj}, 'gpuArray')
% get back from GPU
outputs{jj} = gather(outputs{jj});
end
% processing returns the same number of layers as input
is_block = ndims(outputs{jj}) == 3 && size(outputs{jj},3) == size(inputs{1},3);
if param.use_fp16 && is_block
outputs{jj} = fp16.set(outputs{jj});
end
end
end