__sin2pi intrinisc

Why isn’t there a __sin2pi device code intrinsic which takes the argument in cycles instead of in radians?

It seems like the __sin intrinsic gets mapped to a pair of SASS instruction (at least on lovelace). One which scales by 1/2pi and MUFU.SIN which apparently takes its argument in cycles. But there is not even a ptx instruction sin2pi.approx which takes the argument in cycles.

In my code I evaluate sin(a*x + b) where a and b are pre-computed scaling factors. Of course, it would not be any problem to pre-compute a and b for cycles instead of radians. Looking at the SASS code, it seems like the compiler has troubles optimizing the extra scaling way, so I get an extra instruction, which is kind of silly. Is there a work-around for this? It seems like a common use-case and that it would be rather easy for the NVIDIA engineers to chip in a sin2pi.approx ptx instuction if that is what the hardware typically supports natively…

Generally speaking, functionality is added to CUDA when enough customers (or a sufficiently important customer) request it. You can file a “request for enhancement” (RFE) with NVIDIA through the bug reporting form.

The MUFU hardware changed at the sm_70 stage. Before that, __sinf() mapped to RRO.SINCOS followed by MUFU.SIN, where RRO stands for range reduction operation, which to the best of my knowledge delivers the reduced argument as a fixed-point number. For compute capability >= 7.0 it maps to an FMUL.RZ with 1/(2*PI) followed by MUFU.SIN. Note the explicit rounding mode.RZ (round towards zero) on the FMUL. I am reasonably sure that this prevents it from being merged with the FMUL from your own scaling as part of constant propagation. I tried replacing the regular multiply with an __fmul_rz(), so both multiplies use the same rounding mode, but that does not cause the two scalings to be merged either (which I expected, but wanted to confirm).

I cannot think of a workaround that would allow generating just a plain MUFU.SIN for sm_70 and higher.

[Later:]

One might ask why the emulation sequence for sin.approx.f32 uses FMUL.RZ instead of just regular FMUL. My hypothesis is that this was done to closely or even bit-wise match results on older architectures that used the RRO.SINCOS instruction. While NVIDIA does not provide a precise definition of the functionality of each MUFU instruction, much existing CUDA code was likely implicitly dependent on their exact functionality by that time.

RRO.SINCOS presumably performed a scaling multiply (using the simplest variant available, i.e. without rounding, to minimize cost) plus a floating-point to fixed-point conversion, while RRO.EX2 performed just a floating-point to fixed-point to conversion. When RRO was eliminated in sm_70 (either to simplify the ISA and thus the hardware or reduce dynamic instruction count, or both), the floating-point to fixed-point conversion became part of MUFU.SIN and MUFU.EX2, respectively. Which in case of the sine left the scaling multiplication as a standalone operation.

Thank you for the reply! Then it seem like I will have to live with the extra instruction for a while…

Sorry for my ignorance, but is any optimization done after converting PTX to SASS? If no, I guess it explains why the instruction can’t be optimized away…

Many optimizations only start after/with SASS conversation. E.g. PTX has to work for many architectures, has infinite registers.

Yes. Sorry, I was very imprecise. I was thinking about algebraic optimizations which would change the semantics of the ptx code. In this case one would need to re-associate multiplications in order to fuse the two constants. This would not yield the same result as if the optimization was not done, regardless of rounding mode if I am not mistaken.

And there is the crux of the problem. In general, the CUDA compiler is conservative when it comes to re-associating floating-point arithmetic to preserve the sanity of programmers, faithfulness to IEEE-754 semantics, and the numerical properties of carefully constructed codes. Consider that CUDA’s standard math library is implemented as canned PTX routines that are subject to PTX→ SASS compilation.

ptxas is an optimizing compiler and as such does apply constant propagation. This is generally limited by the “as-if” rule, i.e. the compiled program behaves the same as if it were following the abstract execution semantics defined by the HLL, so generally requires bit-identical transformation. There may be some exceptions when -use_fast_math is used, but I have not pored over much relevant MUFU-heavy code with literal constants lately to say one way or the other.

The new intrinsic device function you envision is worth pitching to NVIDIA via an RFE, IMHO.

Does ptxas even have a --use_fast_math option? It might be that the nvcc option only applies to ptx generation…

I would guess not. I took a look at ptxas --help and didn’t spot anything that looked like it. (There is an FMA contraction option. I’m not suggesting that’s relevant, just indicating that math optimization controls are not completely absent.)

I also tried --use_fast_math --verbose with nvcc and didn’t spot anything that looked likely in the command issued for ptxas there. (FWIW this does show up in the cicc command line: -ftz=1 -prec_div=0 -prec_sqrt=0 -fmad=1 -fast-math)

I filed a RFE (hopefully clear enough).

My bad. I mistakenly remembered that -use_fast_math is being passed through to pxtas as is. I now think what is actually happening is that some of the effects of -use_fastmath are handled in the compilation of CUDA source to PTX, and some relevant internal settings are also passed to ptxas, either as command-line flags (but Robert Crovella’s observations seem to indicate this is not the case) or as attributes in the PTX code. No guarantees that recollection is correct either.

I do recall some lengthy back and forth with the CUDA compiler guys on how to thread relevant compiler settings from nvcc all the way to the backend given the somewhat unusual process of having two optimizing compilers (one of which may be a real-time compiler) bolted together for one compilation flow. But unfortunately, too much time has passed for me to reliably recall the details (I retired from NVIDIA in 2014).

Obviously, much may have changed in the toolchain since my retirement. While I do remain an active user of CUDA, I don’t make a focused effort to keep up with all the new details.

I guess FMA contraction produces bitwise identical results to the non-contracted counterpart. So maybe the design decision is that ptxas only performs bitwise equivalent optimizations and not optimizations which relax numerical precision.

This kind of makes sense for most part, but not really for fast math operations since i figure that these potentially give different results on different architectures anyway…

My experience suggests that is not true. I have seen quite a few counter examples. However I haven’t tried to dissect whether it is an artifact that manifests at PTX compilation, or at some other point in the toolchain. I believe one such counter example is here.

Most certainly not. The difference provides all the crucial advantages of the fused operation. FMA contraction being enabled by default is the one exception to the general rule of avoiding non-bit-accurate re-association in floating-point expressions. This was a conscious design decision, because maximizing the use of FMAs is crucial to computational throughput on GPUs, and it also helps accuracy of floating-point computations on average.

Because this is a deviation from the general principle, a compiler switch -fmad=false was added to the CUDA compiler very early on to facilitate turning off FMA contraction.

not really for fast math operations since i figure that these potentially give different results on different architectures anyway

If by “fast math operations” you mean the MUFU instructions, best I can tell their behavior has not changed since they were first deployed in G80 twenty years ago. Which is a good thing, because a lot of code is likely implicitly dependent on their behavior, in that code has been tested only with the instructions as originally designed, and it is impossible to say how much of it would break if the operations would change behavior.

One could argue that the compiler should optionally be given more leeway in transforming code with MUFU operations, in particular by threading the switch -use_fast_math all the way down to ptxas and defining what precisely the flag would enable at that level, but that is not something that exists now, best I know.

There may be a limited amount of non-bit-accurate constant propagation through MUFU operations today (I haven’t checked; you could check whether that is the case). Independent of MUFU instructions, operations with explicit rounding modes are generally “protected” from non-bit-accurate transformations since it is assumed that there is something “special” going on when these are being used: even with -fmad=true, an FADD and a dependent FMUL with explicit rounding modes will not get contracted into an FMA.

When CUDA was first created, we favored robustness in floating-point computations, as the target was the scientific computation market. Exceptions for performance were made very consciously and only to the degree needed to be competitive with Cg, which applied all sorts of fast & furious code transformations. This was a concession necessary to get CUDA off the ground as a product, as use of Cg was the established way in which early general-purpose computing on GPUs occurred.

This attempt to match Cg’s performance was how device function intrinsics, FMA contraction by default, and the -use_fast_math switch originally came about (note that this switch is so ancient that it does not follow nvcc’s usual command-line flag spelling convention).

Hi dear developers , this is tracked in NVBUG ID 5622094. We will update conclusion here when the ticket is finished.