fortran66のブログ

fortran について書きます。

PGI が CUDA 対応の F2003 コンパイラを11月に出荷?

PGI が CUDA 対応の F2003 コンパイラを11月に出荷とのこと。
http://www.softek.co.jp/SPG/Pgi/Accel/index.html

NVIDIA社の CUDA™ 環境を備えた GPU / GPGPU 対応のコンパイラ機能を含めた PGIアクセラレータ™ コンパイラが、Linux / Windows / MacOS X 版で2009年11月に正式に発売されます。

http://www.pgroup.com/resources/cudafortran.htm
CUDA対応もコメント指示行による自動コンパイルのほかに、直接 NVIDIAAPI 呼び出しにも対応する模様。F2003 対応も素直に読めば完全対応っぽい。

PGI and NVIDIA announced in June 2009 that they are working in cooperation to develop CUDA Fortran. CUDA Fortran includes a Fortran 2003 compiler and tool chain for programming NVIDIA GPUs using Fortran. PGI will support CUDA Fortran with a release of its Fortran compiler later in 2009.

Fortran 用 CUDA インターフェース

イスラエルの企業 GASS 社がFortran 用 CUDA インターフェースを作っているようです。開発中のようですがメールするともらえます。マニュアル無しです。バイナリエディタでライブラリを覗けば、サブルーチン名は分かりますが・・
http://www.gass-ltd.co.il/en/Default.aspx
FORTRAN CUDA
http://www.gass-ltd.co.il/en/products/Fortran.aspx

FORTRAN77 っぽいバインディングです。著作権的にサンプル例を出していいのかよく分かりませんが、能書きを読む限りだしてもよさそうです。

コンパイラ・オプションとしては、小文字化&アンダースコアの付加が必要のようです。ライブラリも Fortran 用と C 用のバッティングを避けるには少し小手先の工夫が必要です。この辺が C 言語との絡みの嫌なところ。Fortran2003 の C-binding が早くすっきりした形で普及してもらいたいです。

/names:lowercase /assume:underscore /libs:dll /threads

  • sample1: Copyright 2008 Company for Advanced Supercomputing Solutions Ltd (GASS).
C* 
* Copyright 2008 Company for Advanced Supercomputing Solutions Ltd
* (GASS).
* All rights reserved.
* http://www.gass-ltd.co.il
*
* NOTICE TO USER:
*
* This source code is subject to GASS ownership rights under U.S. and
* international Copyright laws.  Users and possessors of this source
* code are hereby granted a nonexclusive, royalty-free license to use this
* code in individual and commercial software.
*
* GASS MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE
* CODE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
* IMPLIED WARRANTY OF ANY KIND.  GASS DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR
* PURPOSE.
* IN NO EVENT SHALL GASS BE LIABLE FOR ANY SPECIAL, INDIRECT,
* INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS,  WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION,  ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOURCE CODE.
*
* U.S. Government End Users.   This source code is a "commercial item"
* as that term is defined at  48 C.F.R. 2.101 (OCT 1995), consisting  of
* "commercial computer  software"  and "commercial computer software
* documentation" as such terms are  used in 48 C.F.R. 12.212 (SEPT
* 1995) and is provided to the U.S. Government only as a commercial end item.
* Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through
* 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the
* source code with only those rights set forth herein.
*
* Any use of this source code in individual and commercial software
* must include, in the user documentation and internal comments to the code,
* the above Disclaimer and U.S. Government End Users Notice.
*
      program cuda_fortran_test
      integer dev
      character*256 n
      print *,'Existing CUDA devices:'

      call cuInit(0)
      idevices = 0
      call cuDeviceGetCount(idevices)
      do i=1,idevices
            call cuDeviceGet(dev, i-1)
            call cuDeviceGetName(n, 256, dev)
            print *,'Device ',i-1,': ',n
      enddo
    • 実行結果

  • sample2: Copyright 2008 Company for Advanced Supercomputing Solutions Ltd (GASS).
C*
* Copyright 2008 Company for Advanced Supercomputing Solutions Ltd
* (GASS).
* All rights reserved.
* http://www.gass-ltd.co.il
*
* NOTICE TO USER:
*
* This source code is subject to GASS ownership rights under U.S. and
* international Copyright laws.  Users and possessors of this source
* code are hereby granted a nonexclusive, royalty-free license to use this
* code in individual and commercial software.
*
* GASS MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE
* CODE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
* IMPLIED WARRANTY OF ANY KIND.  GASS DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR
* PURPOSE.
* IN NO EVENT SHALL GASS BE LIABLE FOR ANY SPECIAL, INDIRECT,
* INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS,  WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION,  ARISING OUT OF OR IN CONNECTION WITH THE
* USE OR PERFORMANCE OF THIS SOURCE CODE.
*
* U.S. Government End Users.   This source code is a "commercial item"
* as that term is defined at  48 C.F.R. 2.101 (OCT 1995), consisting  of
* "commercial computer  software"  and "commercial computer software
* documentation" as such terms are  used in 48 C.F.R. 12.212 (SEPT
* 1995) and is provided to the U.S. Government only as a commercial end item.
* Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through
* 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the
* source code with only those rights set forth herein.
*
* Any use of this source code in individual and commercial software
* must include, in the user documentation and internal comments to the code,
* the above Disclaimer and U.S. Government End Users Notice.
*
      program cuda_fortran_run_module
      integer data1(128,16), data2(16), res(128,16)

* Initialize the GPU, use the 1st (0)
      call cuInit(0)
      call cuDeviceGet(idev, 0)
      call cuCtxCreate(ictx, 0, idev)

* Load module and function
      call cuModuleLoad(imod,
     . 'C:\Users\Haru\Documents\Visual Studio 2005\Projects\CudaIl\ForCu
     .da2\test2.cubin')
      call cuModuleGetFunction(ifunc, imod, 'scale')

* Fill arrays
      do i=1,16
         do j=1,128
             data1(j,i) = 128*i + j
         enddo
      enddo
      do i=1,16
             data2(i) = 16-i 
      enddo

* Allocate device pointers
      call cuMemAlloc(idata1_ptr, 16*128*4)
      call cuMemAlloc(idata2_ptr, 16*4)

* Copy data to device
      call cuMemcpyHtoD(idata1_ptr, data1, 16*128*4)
      call cuMemcpyHtoD(idata2_ptr, data2, 16*4)

* Set function parameters
* Function parameters size is 16, because we use 2 pointers in 64 bit
* each being 8 bytes. For 32 platform, this will be 8.
      call cuParamSeti(ifunc, 0, idata1_ptr)
      call cuParamSeti(ifunc, 8, idata2_ptr)
      call cuParamSetSize(ifunc, 16)

* Launch the calculation on the GPU, use the 'y' axis of the block
      call cuFuncSetBlockShape(ifunc, 128, 1, 1)
      call cuLaunchGrid(ifunc, 1, 16, 1)
*      Utility function to check what result returned the last call to
*      a CUDA driver function
*      call GetLastCUDAResult(ires)
*      print *,ires

* Copy the results back
      call cuMemcpyDtoH(res, idata1_ptr, 16*128*4)

* Release all resources
      call cuMemFree(idata1_ptr)
      call cuMemFree(idata2_ptr)

* Verify results
      call verify_data(data1, data2, res)

c      return
      end program

* Verify the results from the GPU
      subroutine verify_data(data1, data2, res)
      integer data1(128,16), data2(16), res(128,16)
      integer test
      
      test = 1
      do i=1,16
         do j=1,128
             if(res(j,i).ne.(data1(j,i)*data2(i)))then
                  print *,res(j,i),(data1(j,i)*data2(i))
                  test=0
             endif
         enddo
      enddo

      if(test.eq.0)then
          print *,'Results do not match'
      else
          print *,'Execution was OK'
      endif

      return
      end
/*
 * Copyright 2008 Company for Advanced Supercomputing Solutions Ltd (GASS).
 * All rights reserved.
 * http://www.gass-ltd.co.il
 *
 * NOTICE TO USER:
 *
 * This source code is subject to GASS ownership rights under U.S. and
 * international Copyright laws.  Users and possessors of this source code
 * are hereby granted a nonexclusive, royalty-free license to use this code
 * in individual and commercial software.
 *
 * GASS MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE
 * CODE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY OF ANY KIND.  GASS DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
 * IN NO EVENT SHALL GASS BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL,
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS,  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION,  ARISING OUT OF OR IN CONNECTION WITH THE USE
 * OR PERFORMANCE OF THIS SOURCE CODE.
 *
 * U.S. Government End Users.   This source code is a "commercial item" as
 * that term is defined at  48 C.F.R. 2.101 (OCT 1995), consisting  of
 * "commercial computer  software"  and "commercial computer software
 * documentation" as such terms are  used in 48 C.F.R. 12.212 (SEPT 1995)
 * and is provided to the U.S. Government only as a commercial end item.
 * Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through
 * 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the
 * source code with only those rights set forth herein.
 *
 * Any use of this source code in individual and commercial software must
 * include, in the user documentation and internal comments to the code,
 * the above Disclaimer and U.S. Government End Users Notice.
 */


/*
 * A simple example that scales values in data1, with the corresponding value
 * from data2.
 */
extern "C" __global__ void scale(int *data1, int *data2)
{
	int idx = blockDim.x * blockIdx.y + threadIdx.x;
	data1[idx] *= data2[blockIdx.y];
}
    • 実行結果

Open64

ところで、CUDA コンパイラの吐くアセンブリ・コードのようなものは、オープンソースの Open64 プロジェクトの成果物を土台にしているようです。このプロジェクトでは Fortran9x コンパイラも作っていたはずなので、NVIDIA も本気を出してくれれば CUDA 用の Fortran コンパイラがすぐにでも出るのではないかと期待してしまいます。
http://www.open64.net/


Fortran で CUDA その3

■Pinned Memory

Fortran で Pinned Memory というものを使うと転送が早くなるというので、それを用いることを考えます。第1巻: CUDAプログラミング入門 (日本語版) http://www.nvidia.co.jp/docs/IO/59373/VolumeI.pdf の 88 ページに記述があります。
まずは NVIDIA 提供のアプリで適正な値を調べておきます。bandwidthtest.exe でオプション -memory=pinned をつけることで調べることが可能です。

■実行結果

CPU 側から GPU 側へメモリー内容を転送し、GPU 内部でコピーを行い、そのコピーしたものを GPU 側から CPU 側に転送して戻してやるということを、3セット*500回行います。これは NVIDIA の入門文書に出てくる典型的な例です。

ここで cudaMalloc で GPU 上に確保できる配列の最大値は 2**24 =64Mbyte でした。またこの大きさの配列は合計5個までしか確保できませんでした。 64Mbyte*5=320Mbyte この数値が何を意味するのか分かりません。
CPU 側での配列の確保は ALLOCATE ではなく、cudaMallocHost で行います。またポインターの結びつけは、NVIDIA の文書にしたがって Fortran2003 の ISO_C_BINDING の機能で行います。Cray 型のポインターより少し厄介です。
転送=コピー=転送のサイクルで 64Mbyte * 3 * 500 = 96Gbyte(片道) のデータを約 40 秒で処理しています。これは約4.8Gbyte/s に相当します。個別の転送過程のみを調べると、CPU−GPU 間の転送は 5.5 Gbyte/sec 前後で行われていることがわかりました。これは、bandwidthtest の結果にほぼ一致します。また CPU→GPU 型の転送の方が GPU→CPU 型よりやや早いなどという傾向も一致します。
その一方 GPU-GPU 間の転送は、CPU側のクロックでは計れなかったので、全過程から CPU→GPUGPU→CPU にかかった時間を差し引くことで見積もりました。その結果は 18Gbyte/sec 程度で bandwidthtest の結果の丁度半分になりました。理由はいまのところ分かりませんが、送・受信でファクター2が出るのかもしれません。

Fortran ソース

必要なライブラリは cudart.lib のみです。すべて Fortran 側から CUDA API を呼び出すので、cu ファイルは必要ありません。

MODULE m_cuda
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
!
ENUM, BIND(C) !cudaMemcpyKind
 ENUMERATOR :: cudaMemcpyHostToHost = 0
 ENUMERATOR :: cudaMemcpyHostToDevice
 ENUMERATOR :: cudaMemcpyDeviceToHost
 ENUMERATOR :: cudaMemcpyDeviceToDevice
END ENUM
!
ENUM, BIND(C) ! cudaError_enum {
 ENUMERATOR :: CUDA_SUCCESS                    = 0
 ENUMERATOR :: CUDA_ERROR_INVALID_VALUE        = 1
 ENUMERATOR :: CUDA_ERROR_OUT_OF_MEMORY        = 2
 ENUMERATOR :: CUDA_ERROR_NOT_INITIALIZED      = 3
 ENUMERATOR :: CUDA_ERROR_DEINITIALIZED        = 4

 ENUMERATOR :: CUDA_ERROR_NO_DEVICE            = 100
 ENUMERATOR :: CUDA_ERROR_INVALID_DEVICE       = 101

 ENUMERATOR :: CUDA_ERROR_INVALID_IMAGE        = 200
 ENUMERATOR :: CUDA_ERROR_INVALID_CONTEXT      = 201
 ENUMERATOR :: CUDA_ERROR_CONTEXT_ALREADY_CURRENT = 202
 ENUMERATOR :: CUDA_ERROR_MAP_FAILED           = 205
 ENUMERATOR :: CUDA_ERROR_UNMAP_FAILED         = 206
 ENUMERATOR :: CUDA_ERROR_ARRAY_IS_MAPPED      = 207
 ENUMERATOR :: CUDA_ERROR_ALREADY_MAPPED       = 208
 ENUMERATOR :: CUDA_ERROR_NO_BINARY_FOR_GPU    = 209
 ENUMERATOR :: CUDA_ERROR_ALREADY_ACQUIRED     = 210
 ENUMERATOR :: CUDA_ERROR_NOT_MAPPED           = 211

 ENUMERATOR :: CUDA_ERROR_INVALID_SOURCE       = 300
 ENUMERATOR :: CUDA_ERROR_FILE_NOT_FOUND       = 301

 ENUMERATOR :: CUDA_ERROR_INVALID_HANDLE       = 400

 ENUMERATOR :: CUDA_ERROR_NOT_FOUND            = 500

 ENUMERATOR :: CUDA_ERROR_NOT_READY            = 600

 ENUMERATOR :: CUDA_ERROR_LAUNCH_FAILED                 = 700
 ENUMERATOR :: CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES       = 701
 ENUMERATOR :: CUDA_ERROR_LAUNCH_TIMEOUT                = 702
 ENUMERATOR :: CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING = 703

 ENUMERATOR :: CUDA_ERROR_UNKNOWN              = 999
END ENUM
!
TYPE, BIND(C) :: t_cudaDeviceProp
 CHARACTER (256) :: name
 INTEGER(C_SIZE_T) :: totalGlobalMem     ! t_size = C_SIZE_T = 8bytes
 INTEGER(C_SIZE_T) :: sharedMemPerBlock
 INTEGER(C_INT) :: regsPerBlock
 INTEGER(C_INT) :: warpSize
 INTEGER(C_SIZE_T) :: memPitch
 INTEGER(C_INT) :: maxThreadsPerBlock
 INTEGER(C_INT) :: maxThreadsDim(3)
 INTEGER(C_INT) :: maxGridSize(3)
 INTEGER(C_INT) :: clockRate
 INTEGER(C_SIZE_T) :: totalConstMem
 INTEGER(C_INT) :: major
 INTEGER(C_INT) :: minor
 INTEGER(C_SIZE_T) :: textureAlignment 
 INTEGER(C_INT) :: deviceOverlap
 INTEGER(C_INT) :: multiProcessorCount
 INTEGER(C_INT) :: kernelExecTimeoutEnabled
 INTEGER(C_INT) :: cudaReserved(39)
END TYPE
!
INTERFACE
 SUBROUTINE cudaGetDeviceCount(n) BIND(C, name='cudaGetDeviceCount') 
  INTEGER, INTENT(OUT) :: n
 END SUBROUTINE cudaGetDeviceCount 
!
 INTEGER FUNCTION cudaGetDeviceProperties(cudaDeviceProp, idev) BIND(C, name='cudaGetDeviceProperties') 
  USE, INTRINSIC :: ISO_C_BINDING
  IMPORT
  TYPE (t_cudaDeviceProp), INTENT(OUT) :: cudaDeviceProp
  INTEGER, VALUE, INTENT(IN) :: idev
 END FUNCTION cudaGetDeviceProperties 
!
 INTEGER FUNCTION cudaSetDevice(idev) BIND(C, name='cudaSetDevice') 
  INTEGER, VALUE, INTENT(IN) :: idev
 END FUNCTION cudaSetDevice 
!
 INTEGER FUNCTION cudaMalloc(ip, nn) BIND(C, name='cudaMalloc')
  USE, INTRINSIC :: ISO_C_BINDING
  TYPE (C_PTR), INTENT(OUT) :: ip
  INTEGER (C_SIZE_T), VALUE, INTENT(IN) :: nn
 END FUNCTION cudaMalloc
!
 INTEGER FUNCTION cudaFree(ip) BIND(C, name='cudaFree')
  USE, INTRINSIC :: ISO_C_BINDING
  TYPE (C_PTR), VALUE, INTENT(IN) :: ip
 END FUNCTION cudaFree
!
 INTEGER FUNCTION cudaMallocHost(ip, nn) BIND(C, name='cudaMallocHost')
  USE, INTRINSIC :: ISO_C_BINDING
  TYPE (C_PTR), INTENT(OUT) :: ip
  INTEGER (C_SIZE_T), VALUE, INTENT(IN) :: nn
 END FUNCTION cudaMallocHost
!
 INTEGER FUNCTION cudaFreeHost(ip) BIND(C, name='cudaFreeHost')
  USE, INTRINSIC :: ISO_C_BINDING
  TYPE (C_PTR), VALUE, INTENT(IN) :: ip
 END FUNCTION cudaFreeHost
!
 INTEGER FUNCTION cudaMemcpy(p_out, p_in, nsize, itype) BIND(C, name='cudaMemcpy')
  USE, INTRINSIC :: ISO_C_BINDING
  TYPE (C_PTR), VALUE, INTENT(IN) :: p_out
  TYPE (C_PTR), VALUE, INTENT(IN) :: p_in
  INTEGER (C_SIZE_T), VALUE, INTENT(IN) :: nsize
  INTEGER (C_INT), VALUE, INTENT(IN) :: itype
 END FUNCTION cudaMemcpy
END INTERFACE
CONTAINS
!---------------------------------------------------------------------
SUBROUTINE printCudaLastError()
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
!
INTERFACE
 INTEGER FUNCTION cudaGetLastError() BIND(C, name='cudaGetLastError')
 END FUNCTION cudaGetLastError
! 
 FUNCTION cudaGetErrorString(n) RESULT(res) BIND(C, name='cudaGetErrorString')
  USE, INTRINSIC :: ISO_C_BINDING
  INTEGER, VALUE, INTENT(IN) :: n
  TYPE (C_PTR) :: res 
 END FUNCTION cudaGetErrorString
END INTERFACE
!
CHARACTER (512), POINTER :: buff 
TYPE (C_PTR) :: ibuff
INTEGER :: nerr, nlen
nerr = cudaGetLastError()
ibuff = cudaGetErrorString( nerr )
CALL C_F_POINTER(ibuff, buff)
nlen = INDEX(buff, ACHAR(0)) - 1
PRINT '(a)', buff(1:nlen)
RETURN
END SUBROUTINE printCudaLastError
!---------------------------------------------------------------------
SUBROUTINE CheckCudaRet(iret, text)
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
INTEGER, INTENT(IN) :: iret
CHARACTER (*), INTENT(IN), OPTIONAL :: text
IF (iret /= CUDA_SUCCESS) THEN
 IF (PRESENT(text)) PRINT '(a)', text
 CALL printCudaLastError()
 STOP
END IF
RETURN
END SUBROUTINE CheckCudaRet
!---------------------------------------------------------------------
SUBROUTINE gpu_info()
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
TYPE (t_cudaDeviceProp) :: prop
INTEGER :: idev, ndev, iret
!
CALL cudaGetDeviceCount(ndev)
PRINT *, 'Number of Device(s) =', ndev 
DO idev = 0, ndev - 1
 iret = cudaGetDeviceProperties(prop, idev)
 CALL CheckCudaRet(iret, 'cudaGetDeviceProperties')
 PRINT '(1x, a, i4)', 'Device No.:', idev
 PRINT '(1x, a, a  )', 'name:                     ', prop%name( 1:INDEX(prop%name, ACHAR(0)) - 1 )
 PRINT '(1x, a25, i10, a, i5, a)', 'totalGlobalMem:          ', prop%totalGlobalMem, ' bytes =', &
                                     prop%totalGlobalMem / 2**20, ' Mbytes' 
 PRINT '(1x, a25, i10, a, i5, a)', 'sharedMemPerBlock:       ', prop%sharedMemPerBlock, ' bytes =', &
                                     prop%sharedMemPerBlock / 2**10, ' Kbytes'
 PRINT '(1x, a25, i10, a, i5, a)', 'regsPerBlock:            ', prop%regsPerBlock, ' bytes =', &
                                     prop%regsPerBlock / 2**10, ' Kbytes'
 PRINT '(1x, a25, i10)', 'warpSize:                ', prop%warpSize
 PRINT '(1x, a25, i10)', 'memPitch:                ', prop%memPitch
 PRINT '(1x, a25, i10)', 'maxThreadsPerBlock:      ', prop%maxThreadsPerBlock
 PRINT '(1x, a25, 3i10)', 'maxThreadsDim:           ', prop%maxThreadsDim
 PRINT '(1x, a25, 3i10)', 'maxGridSize:             ', prop%maxGridSize
 PRINT '(1x, a25, i10, a, i5, a)', 'totalConstMem:           ', prop%totalConstMem, ' bytes =', &
                                     prop%totalConstMem / 2**10, ' Kbytes'                        
 PRINT '(1x, a25, 2i10)', 'major, minor:            ', prop%major, prop%minor
 PRINT '(1x, a25, i10, a, f8.2, a)', 'clockRate:               ', prop%clockRate, ' Hz =', &
                                     prop%clockRate / 1.0e6, ' GHz'
 PRINT '(1x, a25, i10)', 'textureAlignment:        ', prop%textureAlignment 
 PRINT '(1x, a25, i10)', 'deviceOverlap:           ', prop%deviceOverlap
 PRINT '(1x, a25, i10)', 'multiProcessorCount:     ', prop%multiProcessorCount
 PRINT '(1x, a25, i10)', 'kernelExecTimeoutEnabled:', prop%kernelExecTimeoutEnabled
 PRINT *
END DO
END SUBROUTINE gpu_info
!---------------------------------------------------------------------
END MODULE m_cuda
!=========================================================================
PROGRAM CudaTest
USE, INTRINSIC :: ISO_C_BINDING
USE :: m_cuda
IMPLICIT NONE
!
!define length of the array
INTEGER, PARAMETER :: N = 2**24
REAL (8) :: t0, t1, tt0, tt1
CHARACTER(10) :: time0, time1
INTEGER :: i, iret, ic0, ic1, icrate
TYPE (C_PTR) :: h_c, h_b, d_p, d_q, d_r(10)
REAL (C_FLOAT), POINTER:: c(:), b(:)
INTEGER (C_SIZE_T) :: nn
!
CALL gpu_info()
iret = cudaSetDevice( 0 )
CALL CheckCudaRet(iret, 'cudaSetDevice')
!
nn = sizeof(C_FLOAT) * n
PRINT *, nn, 'bytes ', nn / 2**20, 'Mbytes' 

iret = cudaMallocHost(h_c, nn)
CALL CheckCudaRet(iret, 'cudaMallocHost1')
CALL C_F_POINTER( h_c, c, [n] )
iret = cudaMallocHost(h_b, nn)
CALL CheckCudaRet(iret, 'cudaMallocHost2')
CALL C_F_POINTER( h_b, b, [n] )
!
c = 1.0
!
iret = cudaMalloc(d_p, nn)
CALL CheckCudaRet(iret, 'cudaMalloc1')
iret = cudaMalloc(d_q, nn)
CALL CheckCudaRet(iret, 'cudaMalloc2')

DO i = 1, 3
 iret = cudaMalloc(d_r(i), nn)
 PRINT *, 'Alloc ', i
 CALL CheckCudaRet(iret, 'cudaMalloc3')
END DO
!
CALL CPU_TIME(t0)
CALL SYSTEM_CLOCK(ic0)
CALL date_and_time(time = time0)
DO i = 1, 500
 iret = cudaMemcpy( d_p, h_c, nn, cudaMemcpyHostToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy1')
 iret = cudaMemcpy( d_q, d_p, nn, cudaMemcpyDeviceToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy2')
 iret = cudaMemcpy( h_b, d_q, nn, cudaMemcpyDeviceToHost )
 CALL CheckCudaRet(iret, 'cudaMemcpy3')
!
 iret = cudaMemcpy( d_r(1), h_c, nn, cudaMemcpyHostToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy1')
 iret = cudaMemcpy( d_r(2), d_r(1), nn, cudaMemcpyDeviceToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy2')
 iret = cudaMemcpy( h_b, d_r(2), nn, cudaMemcpyDeviceToHost )
 CALL CheckCudaRet(iret, 'cudaMemcpy3')
!
 iret = cudaMemcpy( d_r(3), h_c, nn, cudaMemcpyHostToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy1')
 iret = cudaMemcpy( d_r(1), d_r(3), nn, cudaMemcpyDeviceToDevice )
 CALL CheckCudaRet(iret, 'cudaMemcpy2')
 iret = cudaMemcpy( h_b, d_r(1), nn, cudaMemcpyDeviceToHost )
 CALL CheckCudaRet(iret, 'cudaMemcpy3')
END DO
CALL date_and_time(time = time1)
CALL CPU_TIME(t1)
CALL SYSTEM_CLOCK(ic1, icrate)
!
READ(time0, '(F10.3)') tt0
READ(time1, '(F10.3)') tt1
PRINT *, 'Copy Host->GPU->GPU->HOST: cpu_time = ', t1 - t0, 'sec'
PRINT *, '                      DATE_AND_TIME = ', tt1 - tt0, 'sec' !, time0, time1
!
IF ( ANY(c /= b) ) THEN 
 PRINT *, 'error: mismatch!'
ELSE
 PRINT *, 'Copy Check OK', SUM(b) * sizeof(C_FLOAT), KIND(1.0_C_FLOAT)
END IF
!
iret = cudaFree(d_p)
CALL CheckCudaRet(iret, 'cudaFree')
iret = cudaFree(d_q)
CALL CheckCudaRet(iret, 'cudaFree')
DO i = 1, 3
 iret = cudaFree(d_r(i))
 PRINT *, 'Free ', i
 CALL CheckCudaRet(iret, 'cudaFree')
END DO
!
iret = cudaFreeHost(h_c)
CALL CheckCudaRet(iret, 'cudaFreeHost')
iret = cudaFreeHost(h_b)
CALL CheckCudaRet(iret, 'cudaFreeHost')
!
STOP
END PROGRAM CudaTest

Fortran で CUDA その2  台形積分

またぞろ簡単素朴な積分をやってみます。
4  \int_0^1 {1\over 1+x^2}dx = \pi
を計算します。f(x)={1\over 1+x^2}GPU の各スレッドごとに異なる x について計算してもらって、積分範囲全域での被積分関数の値を配列に一気に返してもらいます。

実行結果

Fortran ソース

前半は Fortran 側で CUDA ルーチンを直接呼んで、ビデオカードの情報を得ています。

後半では、簡単な台形積分をやって π を求めています。各積分点ごとのローレンツ型関数の値を GPU 側でスレッド並列で計算し配列に返してもらいます。簡単のため CPU側(Fortran 側)で総和処理をしています。

h を渡したのに使ってないとか、色々無駄がありますが勘弁してもらって・・

MODULE m_cuda
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
!
TYPE, BIND(C) :: t_cudaDeviceProp
 CHARACTER (256) :: name
 INTEGER(C_LONG_LONG) :: totalGlobalMem     ! t_size = C_LONG_LONG = 8bytes
 INTEGER(C_LONG_LONG) :: sharedMemPerBlock
 INTEGER(C_INT) :: regsPerBlock
 INTEGER(C_INT) :: warpSize
 INTEGER(C_LONG_LONG) :: memPitch
 INTEGER(C_INT) :: maxThreadsPerBlock
 INTEGER(C_INT) :: maxThreadsDim(3)
 INTEGER(C_INT) :: maxGridSize(3)
 INTEGER(C_INT) :: clockRate
 INTEGER(C_LONG_LONG) :: totalConstMem
 INTEGER(C_INT) :: major
 INTEGER(C_INT) :: minor
 INTEGER(C_LONG_LONG) :: textureAlignment 
 INTEGER(C_INT) :: deviceOverlap
 INTEGER(C_INT) :: multiProcessorCount
 INTEGER(C_INT) :: kernelExecTimeoutEnabled
 INTEGER(C_INT) :: cudaReserved(39)
END TYPE
!
INTERFACE
 SUBROUTINE cudaGetDeviceCount(n) BIND(C, name='cudaGetDeviceCount') 
 INTEGER, INTENT(OUT) :: n
 END SUBROUTINE cudaGetDeviceCount 
!
 INTEGER FUNCTION cudaGetDeviceProperties(cudaDeviceProp, idev) BIND(C, name='cudaGetDeviceProperties') 
 USE, INTRINSIC :: ISO_C_BINDING
 IMPORT
 TYPE (t_cudaDeviceProp), INTENT(OUT) :: cudaDeviceProp
 INTEGER, VALUE, INTENT(IN) :: idev
 END FUNCTION cudaGetDeviceProperties 
!
 INTEGER FUNCTION cudaSetDevice(idev) BIND(C, name='cudaSetDevice') 
 INTEGER, VALUE, INTENT(IN) :: idev
 END FUNCTION cudaSetDevice 
END INTERFACE
END MODULE m_cuda
!======================================================
PROGRAM CudaTest
USE, INTRINSIC :: ISO_C_BINDING
USE :: m_cuda
IMPLICIT NONE
!
INTERFACE 
 SUBROUTINE cudafunction(n, h, c) BIND(C, name='cudafunction')
 USE, INTRINSIC :: ISO_C_BINDING
 IMPORT
 INTEGER      , INTENT(IN) :: n
 REAL (C_FLOAT), INTENT(IN) :: h
 REAL (C_FLOAT), INTENT(OUT):: c(N)
 END SUBROUTINE cudafunction
END INTERFACE
!
TYPE (t_cudaDeviceProp) :: prop
!
!define length of the array
INTEGER, PARAMETER :: N = 1024
!
REAL (C_FLOAT) :: c(N)
REAL :: s, h
INTEGER :: i, idev, ndev, iret
!
CALL cudaGetDeviceCount(ndev)
PRINT *, 'Number of Device(s) =', ndev 
DO idev = 0, ndev - 1
 iret = cudaGetDeviceProperties(prop, idev)
 IF (iret /= 0) THEN 
  PRINT '(a, i6)', 'cudaGetDeviceProperties: error code = ', iret
  STOP
 END IF
 PRINT '(1x, a, i4)', 'Device No.:', idev
 PRINT '(1x, a, a  )', 'name:                     ', prop%name( 1:INDEX(prop%name, ACHAR(0)) - 1 )
 PRINT '(1x, a25, i10, a, i5, a)', 'totalGlobalMem:          ', prop%totalGlobalMem, ' bytes =', &
                                     prop%totalGlobalMem / 2**20, ' Mbytes' 
 PRINT '(1x, a25, i10, a, i5, a)', 'sharedMemPerBlock:       ', prop%sharedMemPerBlock, ' bytes =', &
                                     prop%sharedMemPerBlock / 2**10, ' Kbytes'
 PRINT '(1x, a25, i10, a, i5, a)', 'regsPerBlock:            ', prop%regsPerBlock, ' bytes =', &
                                     prop%regsPerBlock / 2**10, ' Kbytes'
 PRINT '(1x, a25, i10)', 'warpSize:                ', prop%warpSize
 PRINT '(1x, a25, i10)', 'memPitch:                ', prop%memPitch
 PRINT '(1x, a25, i10)', 'maxThreadsPerBlock:      ', prop%maxThreadsPerBlock
 PRINT '(1x, a25, 3i10)', 'maxThreadsDim:           ', prop%maxThreadsDim
 PRINT '(1x, a25, 3i10)', 'maxGridSize:             ', prop%maxGridSize
 PRINT '(1x, a25, i10, a, i5, a)', 'totalConstMem:           ', prop%totalConstMem, ' bytes =', &
                                     prop%totalConstMem / 2**10, ' Kbytes'                        
 PRINT '(1x, a25, 2i10)', 'major, minor:            ', prop%major, prop%minor
 PRINT '(1x, a25, i10, a, f8.2, a)', 'clockRate:               ', prop%clockRate, ' Hz =', &
                                     prop%clockRate / 1.0e6, ' GHz'
 PRINT '(1x, a25, i10)', 'textureAlignment:        ', prop%textureAlignment 
 PRINT '(1x, a25, i10)', 'deviceOverlap:           ', prop%deviceOverlap
 PRINT '(1x, a25, i10)', 'multiProcessorCount:     ', prop%multiProcessorCount
 PRINT '(1x, a25, i10)', 'kernelExecTimeoutEnabled:', prop%kernelExecTimeoutEnabled
 PRINT *
END DO
idev = 0
iret = cudaSetDevice(idev)
IF (iret /= 0) THEN
 PRINT *, 'cudaSetDevice: error code =', iret
 STOP
END IF
!
h = (1.0 - 0.0) / (N - 1)
! Fortran -> C -> CUDA ->C ->Fortran
CALL cudafunction(N, h, c)
!
!print *, c
!
s = h * ( SUM(c) - 0.5 * (c(1) + c(N)) ) ! Trapezoidal integral : Int_0^1  1/ (1 + x^2) dx = pi / 4 
PRINT *, 'PI =', s * 4.0
STOP
END PROGRAM CudaTest

CU ソース

#include <stdio.h>
#include "cuda.h"


/* Define CUDA kernel that squares the input complex array */
__global__ void  lorentz(float *in, float *out, int N)
{
 unsigned int index = blockIdx.x * blockDim.x + threadIdx.x;
 float x = (float) index / (float) (N - 1);
 if( index<N ) 
  {
   out[index] = 1.0 / (1.0 + x * x);
  }

}


/* 
   Fortran subroutine arguments are passed by references.   
   call fun( array_a, array_b, N) will be mapped to
   function (*a, *b, *N);
*/
extern "C" void cudafunction(int *Np, float *h, float *a)
{
  int block_size=4;
  float *a_d;
  int N=*Np;
  /* Allocate complex array on device */
  cudaMalloc ((void **) &a_d , sizeof(float)*N);
  
//  /* Copy array from host memory to device memory */
//  cudaMemcpy( a_d, a,  sizeof(float)*N   ,cudaMemcpyHostToDevice);

  /* Compute execution configuration */
   dim3 dimBlock(block_size);
   dim3 dimGrid (N/dimBlock.x);
   if( N % block_size != 0 ) dimGrid.x+=1;

  /* Execute the kernel */
  lorentz<<<dimGrid, dimBlock>>>(a_d, a_d, N);

  /* Copy the result back */
   cudaMemcpy( a, a_d, sizeof(float) * N, cudaMemcpyDeviceToHost );  

  /* Free memory on the device */
  cudaFree(a_d);

  return;
}

Fortran で CUDA

Fortran で CUDA を試みるチラシ裏メモ帳です。NVIDIA の CUDA ZONE にいつの間にか Fortran の項目が出来ていたのでいじってみました。Vista 64bit なので苦難の道です涙目です。
http://www.nvidia.co.jp/object/cuda_programming_tools_jp.html

■溝浚い

Dr.Dobbs Journal の『大衆のためのスーパーコンピューティング』の記事が、ドブ博士の論説『質量のためのスーパーコンピューティング』と訳されているのを見ると(Mass は Mass に違いないが)この道を進んでいいのか一抹の不安というか市松模様の不安を覚えます。
http://www.nvidia.co.jp/object/cuda_education_jp.html

■メモ

CUDA 対応 Fortran コンパイラは PGI がやっています。現在 64bit版の Linux 用のテストが希望者の間で行われているようです。
http://www.pgroup.com/resources/accel.htm
今後を注目したいですが、今回はパス。

■本題

□ドライバ等のインストール

CUDA ZONE
http://www.nvidia.co.jp/object/cuda_get_jp.html

  1. CUDA 2.1
    1. CUDAドライバ: Microsoft Windows Vista 64ビット用CUDAサポートNVIDIAドライバ(181.20)。
    2. CUDAツールキット: Windows Vista 64ビット用CUDAツールキット バージョン2.1
    3. CUDA SDK: Windows Vista 64ビット用CUDA SDK バージョン2.1

ツールキットのインストーラが途中で止まりましたが、インストールは済んでいるようなので「ままよ!」と進むことにします。

□check

CUDA_2_Quickstart_Guide.PDF
http://www.nvidia.co.jp/object/cuda_develop_jp.html

  1. インストールが成功していれば、bandwidthtest.exe を任意のディレクトリから実行できるはずです。


CPU / GPU 間は約 4.5 Gbytes/s、GPU 内部では約 36Gbytes/s のメモリー転送スピードということでしょうか?

□VS2005との統合
  • カスタムビルド

ここで落とせるカスタムビルド用規則を使うほうがシンプルでよい気がします。NVIDIA の Forum の ID が必要になります。
http://forums.nvidia.com/index.php?showtopic=30273&st=0
http://forums.nvidia.com/index.php?act=attach&type=post&id=3945
落としてきた Zip を解凍後、VS2005 で空のプロジェクトファイルを作成後、右クリック、カスタムビルド規則、既存ファイル検索 で VS2005 のディレクトリが開くので、解凍したフォルダーを突っ込んでコピー、そのディレクトリに下りて cudarules を選択してやれば、以後 CUDA の選択肢がカスタムビルドのメニューに現れるようになります。

-Wizard
http://sourceforge.net/projects/cudavswizard
これをインストールすると、C++用のプロジェクトに CUDA 用の選択肢が追加されます。
しかし、この wizard で生成された CUDA プログラムを実行すると VS2005 が終了時に落ちるようになりました。「ままよ!」と見過ごすことにします。

  • シンタックス・カラーリング

NVIDIAが用意してくれているものを用います。手順は、C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\doc\syntax_highlighting\visual_studio_8\README.txt にあるままに。

Want pretty syntax highlighting when editing your .cu files in Visual Studio?
Heres how:

    • -

Visual Studio .Net 2005 / Visual Studio 8:
1. If you don't have a usertype.dat file in your "Microsoft Visual Studio 8\Common7\IDE" folder, then copy the included usertype.dat file there. If you do, append the contents of the included usertype.dat onto the end of the "Microsoft Visual Studio 8\Common7\IDE\usertype.dat"

2. Start Visual Studio 8. Select the menu "Tools->Options...". Open "Text Editor" in the tree view on the left, and click on "File Extension". Type cu in the "Extension" box, and click "Add". Click "OK" on the dialog box.

3. Restart Visual Studio and your CUDA code should now have syntax highlighting.

□ディレクトリパスの設定

ツール->オプション から


C:\CUDA\lib
C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib


C:\CUDA\include\

C:\CUDA\lib
C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib

□プロジェクトにおける構成マネージャの設定

死ぬほどイライラする構成マネージャの設定に注意。64bit 設定の場合はすべて x64 に。x64 にしたつもりが win32 のままだったり、許しがたい行動を取って呉れます。Windows の検索の犬やオフィスのヘルプのイルカ並みに殺意を覚えます。

□実例

  • CUDA ZONE プログラミングツール http://www.nvidia.co.jp/object/cuda_programming_tools_jp.html から『CUDAカーネルに対するFORTRANインターフェイス (Fortran_Cuda.tgz)』を落としてきます。使うのは中にある main.f90 と Cuda_function.cu のふたつです。
  • Fortran のコンソールプロジェクトをつくり、main.f90 を登録します。追加のライブラリとして cudart.lib を加えます。ここではプロジェクト名を FortranCuda としました。

  • CUDA スタティック・ライブラリのプロジェクトをつくります。ここでは、デフォールト名のCUDAWinApp1 にしています。ヘッダーファイルなどは消して、sample.cu を Cuda_function.cu で置き換えます。ソリューション・エクスプローラを見ると、なぜかソースファイルがソースファイルの項目の外に作られてしまいますが、そのままでもよし、移動させてもよし、適宜対応します。
  • Fortran プロジェクトを右クリックして依存関係の設定を選びます。CUDA プロジェクトを選択します。

  • 本来ならばこれで設定終了ですが、ソースファイルが g77用の設定になっているようなので、多少 Fortran ソースを手直しする必要があります。インターフェースを用意することで解決します。
INTERFACE 
 SUBROUTINE cudafunction(c, c2, i) BIND(C, name='cudafunction_')
 USE, INTRINSIC :: ISO_C_BINDING
 IMPORT
 COMPLEX(C_FLOAT_COMPLEX) :: c(N), c2(N)
! complex(C_FLOAT_COMPLEX) :: c(:), c2(:) ! no good
 INTEGER :: i
 END SUBROUTINE cudafunction
END INTERFACE

ここで、サブルーチンの配列引数のサイズは明示的に与えなければならないようです。

  • Compile & Go

あとは、通常のごとくビルドして実行できるはずです。

(必須事項はすべて書いたつもりですが、試行錯誤の上ここまで来たので、何か抜けているかもしれません。)