-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hybrid execution of sterf #865
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changelog ok
If uplo = rocblas_fill_upper, only the upper triangular part is copied | ||
If uplo = rocblas_fill_lower, only the lower triangular part is copied **/ | ||
template <typename T, typename U, typename Mask = no_mask> | ||
ROCSOLVER_KERNEL void copy_mat(copymat_direction direction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor suggestion: if these is intended to be a frequently reused library routine, perhaps we might consider a more general version from (A,shiftA,lda,strideA) to (B,shiftB,ldb,strideB). The special case of a linear "buffer" may be shiftB = 0, strideB = (m*n), ldb = m; By using a more general interface, perhaps we don't need the extra enum for "direction". We just need to reverse the order of the "A" and "B" arguments.
Perhaps consider a more suggestive name for Mask. It is not immediately obvious to me whether we perform the copy if the mask is non-zero, or skip the copy if the mask is non-zero.
Is there a convention to place action arguments before the data? Say in TRSM, we place the uplo, side, trans, diag, arguments before the data, instead of placing the diag or uplo at the end of subroutine argument list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC, this function has multiple overloads where B has fewer associated parameters than A. In these cases, specifying a direction is necessary since we can't easily swap A and B. It's not necessary for this particular overload, but I think we kept it for consistency.
I don't entirely remember how the mask works either, so we should definitely improve documentation at a minimum.
Conventionally, yes, those arguments are placed at the start, but when they have default values they can't be put before any arguments without default values. I seem to remember they were tacked on after the fact, and assigned default values to avoid breaking existing calls.
T* Ap = load_ptr_batch<T>(A, b, shiftA, strideA); | ||
T* Bp = &buffer[b * strideB]; | ||
|
||
if(direction == copymat_to_buffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether this library routine can be further generalized to add a "trans" option to perform conjugate_transpose or transpose or none. Magma blas has a routine to perform efficient matrix transpose. Just a suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a copy_trans_mat function that does have this functionality.
ROCSOLVER_BEGIN_NAMESPACE | ||
|
||
template <typename T, typename I, typename U> | ||
struct rocsolver_hybrid_array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if there is more documentation or description not of the internal implementation details but on the interface what it is intended to do and what functions or method are available. Is it keeping a data structure (strided array) on CPU and mirror it on GPU device, or vice versa? If so, perhaps the routines are trying to "sync"? Should there be "async" and "noasync" versions since the name of the routine "_async" is suggestive there is a noasync version. Are there calls to synchronize stream in the "_async" routines? If these are not truyly "async" routines, perhaps just leave out "_async"? I wonder whether the std::vector can be leveraged so there is less worry about memory leaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the documentation is rather sparse at the moment. I can take another look and try to improve it.
I added the _async suffix mostly to indicate to anyone using the function that a hipStreamSynchronize needs to be done before using the results of the function. I could have synchronized within the functions themselves, but I wanted to give us the option of queuing a number of hipMemcpys that can be sync'ed all at once for better performance.
@@ -103,11 +105,19 @@ class SYEV_HEEV : public ::TestWithParam<syev_heev_tuple> | |||
} | |||
}; | |||
|
|||
class SYEV : public SYEV_HEEV | |||
class SYEV : public SYEV_HEEV<0> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: is there a convention to use all upper case for compile time constant or #define macros or constants? If so, perhaps consider using a mixed case? Just a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that changing the capitalization of the test class will also change the capitalization of the test suite output. I like the all caps text in the test output as it makes it very easy to pick out the function name.
if(batch_array && (val_array || this->dim < 0)) | ||
free(batch_array); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this class owns malloc-ed memory it might be a good idea to delete its implicit copy constructor and assignment operator to guard against a double free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nicer to follow the rule of 5 also explicitly default
or delete
the move constructor and the move assignment operator (those are implicitly deleted here, but you could also default
them without any issues if this bodes well with your idea of how the struct is meant to be used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether using thrust::host_vector and thrust::device_vector would potentially simplify the code. This is in the spirit of "eating our own dog food" or "drinking our own champagne" in re-using AMD rocThrust software. Just a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good, thanks @tfalders!
The rocsolver_hybrid_array
struct complements the implementation of the current hybrid methods well. I've just included a few comments there, let me know if you want me to clarify anything.
|
||
/* Used to read device pointers from a batched array for use on the host; no other data is read from the | ||
device. */ | ||
rocblas_status init_pointers_only(U array, rocblas_stride stride, I batch_count, hipStream_t stream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comment (code design): the behaviour of this struct when initialized with init_pointers_only
is so different from the bahaviour yielded by init_async
that it makes sense to break it apart into two different structs. Maybe something to be pondered for the future?
I dim, batch_count; | ||
rocblas_stride stride; | ||
|
||
U src_array; | ||
T** batch_array; | ||
T* val_array; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor question (code design): what would be the use case in which we would want to access those members directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe there is a use case. Would you like me to mark them as private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pointed it out to start the conversation, I think that we will know better than to try and change those by hand; but it is always safer to constrain degrees of freedom that are not meant to be used.
Whether to keep or change the access type of those is a decision that I defer to you, I trust your judgement either way.
cfc9769
to
d990cc9
Compare
if(!val_array) | ||
return rocblas_status_memory_error; | ||
#else | ||
if(posix_memalign((void**)&val_array, sizeof(void*), val_bytes) != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small comment here: sizeof(void*)
is just uintptr_t
, so this is not really different from a malloc
, more typical alignments would be 32
or 64
. But I see the point in leaving this as is and deciding the final value after benchmarking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good to me, thanks again @tfalders! I left a couple of comments but nothing that requires any update at this point in time -- unless you want to update anything, of course.
|
||
auto istat = hipPointerGetAttributes(&dev_attributes, ptr); | ||
if(istat != hipSuccess) | ||
fmt::print(stderr, "is_device_pointer: istat = {} {}\n", istat, hipGetErrorName(istat)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just noticed that this line is not compiling in Windows, likely because CI is using a version of libfmt that is newer than 10.0.0. Among many other things, that version deprecated the implicit conversion of enums, thus the simplest fix would be to update this line into something like:
fmt::print(stderr, "is_device_pointer: istat = {} {}\n", static_cast<std::int32_t>(istat), hipGetErrorName(istat));
Their documentation provides other options, but we should consider updating rocSOLVER to make those errors easier to catch going forward (of course, not in this PR). For the time being, please make sure to cast all inputs of fmt::print
into basic types, or types defined in the std
namespace.
This is a revamp of #462 using the new hybrid infrastructure that was introduced for GESVD. Here's a brief summary of changes: