CUDA 内核本地调试环境设置

要对 CUDA 内核进行调试,我们需要安装 nVidia Parallel Nsight 软件包。该软件自带一个说明文件,其中详细描述了如何设置调试环境。但有些细节说得还不够具体,这里将我设置调试环境中的一些过程进行一下记录。

准备

首先,调试环境分两种:远程和本地。远程模式需要有一台专门用于 CUDA 计算的机器,相当于有一个计算服务器。而对于我们独立开发者来说,最方便的是设置一个本地调试环境。所以本文将只关注本地调试环境的设置。

对于本地调试,还需要一个前提,那就是你至少需要2块显卡,一块用于显示你的桌面环境和 Visual Studio,另一块则需要比较新的支持 CUDA 的显卡,用来执行 CUDA 内核。比如我的环境如下:

  • 一块板载的 GeForce 9300 (用于桌面环境的显示)
  • 一块独立的 GeForce GTX 260 (用于 CUDA 内核的执行和调试)

设置

设置之前,请仔细阅读 Nsight 用户手册中的《Setup Local Debugging》和《Setup Local Headless GPU Debugging》这两章。

Headless 的意思是指,关闭你用了执行 CUDA 的显卡的所有显示输出。一般情况下,一旦我们将线缆连接上了显卡的 AVI 或是 HDMI 接口后,不管那端连接的显示器是否开启,我们的显卡都处于可以输出显示的模式。而 Headless 就是要关闭所有这些显示。

设置的过程大致如下:

  • 关闭驱动的 TDR。这个可以通过 Nsight Options 窗口完成。
  • 关闭 D3D WPF 加速。通过导入 Nsight 安装目录里的 DisableWpfHardwareAcceleration.reg 注册表完成。
  • 设置一个环境变量 CUDA_VISIBLE_DEVICES

前两项手册里都有详细的操作说明,但是关于设置 CUDA_VISIBLE_DEVICES,手册说得并不是很清楚。所以我这里详细讲解下。

首先,你要弄清你系统里的显示设备的顺序。这个可以执行 SDK 中的 deviceQueryDrv.exe 来查看。比如在我的系统中,设备顺序是:

  • 设备0: GeForce GTX 260
  • 设备1: GeForce 9300 / nForce 730i

那么,如果我想用 GTX 260 进行调试,我就应该设置 CUDA_VISIBLE_DEVICES=0。

在完成了这些设置后,需要重启一下电脑来使设置生效。

调试

每次进行调试前,我们需要:

  • 启动 Nsight Monitor。
  • 关闭调试用显卡的显示输出,使其进入 Headless 模式。
  • 关闭 Windows 的视觉效果。这个可以点击开始,然后右击“计算机”,选择“属性”,然后选择“高级系统设置”,点击设置“性能”,去掉“在窗口和按钮上使用视觉样式”前的勾。
  • 再次确认 CUDA_VISIBLE_DEVICES 的设置,让其仅允许 Headless 模式的显卡对 CUDA 可见。

调试时,使用 Visual Studio IDE 在内核里设置断点。由于内核是并行执行的,所以很多时候,我们需要右击断点,选择设置条件断点。比如下图就是设置了条件断点来分析处理第5个数据的内核执行:

右击项目,选择 Debug -> Start CUDA Debugging ,就可以开始进行内核调试了。

最后,在完成了调试后,你可以讲 CUDA_VISIBLE_DEVICES 重新设置成所有的设备,比如:CUDA_VISIBLE_DEVICES=0,1 ,以方便测试多设备之间的协作。

F# 中命令行参数解析

编写命令行程序,参数的解析不可避免。然而,F# 基础库并没有提供对此的支持。不过,F# PowerPack 提供了一个简单的工具来帮助解析命令行参数。其使用优雅简单,符合 F# 描述型语言的特性。

首先,让我用一个模拟编译器命令行程序来展示下该工具的使用:

open Microsoft.FSharp.Text

let mutable output = "a.out"
let verbose = ref false
let mutable warningLevel = 0

let mutable objs:string list = []
let compile s =
    match !verbose with
    | false -> printfn "compiling %s ..." s
    | true -> printfn "compiling %s with -w %d ..." s warningLevel
    objs <- (sprintf "%s.o" s)::objs

let specs =
    [ ("-o",
       ArgType.String (fun s -> output <- s),
       "name of the output")
      ("-v",
       ArgType.Set verbose,
       "display addtional information")
      ("-w",
       ArgType.Int (fun i -> warningLevel <- i),
       "set warning level")
    ] |> List.map (fun (sh, ty, desc) -> ArgInfo(sh, ty, desc))

ArgParser.Parse(specs, compile)

match objs with
| [] -> printfn "no object files found!"
| _ ->
    printfn "linking ..."
    objs |> Set.ofList |> Set.iter (fun x -> printfn "-> %s" x)
    printfn "generate output %s" output

也就不用多解释了,F# 描述性的特性让我们很容易地就能看懂这段代码。让我们来看看此段代码的执行结果,注意,该工具自动生成了 --help 命令:

>CmdArg.exe a.c b.c
compiling a.c ...
compiling b.c ...
linking ...
-> a.c.o
-> b.c.o
generate output a.out

>CmdArg.exe -v a.c b.c
compiling a.c with -w 0 ...
compiling b.c with -w 0 ...
linking ...
-> a.c.o
-> b.c.o
generate output a.out

>CmdArg.exe -v -w 3 a.c b.c
compiling a.c with -w 3 ...
compiling b.c with -w 3 ...
linking ...
-> a.c.o
-> b.c.o
generate output a.out

>CmdArg.exe -v -w 3 -o test.exe a.c b.c
compiling a.c with -w 3 ...
compiling b.c with -w 3 ...
linking ...
-> a.c.o
-> b.c.o
generate output test.exe

>CmdArg.exe --help

        -o <string>: name of the output
        -v: display addtional information
        -w <int>: set warning level
        --help: display this list of options
        -help: display this list of options

该工具提供的参数类型并不多,简介如下:

  • ArgType.Unit(f): 启动一个 unit -> unit 的回调函数
  • ArgType.Set(b): 自动设置一个 bool reftrue
  • ArgType.Clear(b): 自动设置一个 bool reffalse
  • ArgType.String(f): 启动一个 string -> unit 的回调函数
  • ArgType.Int(f): 启动一个 int -> unit 的回调函数
  • ArgType.Float(f): 启动一个 float -> unit 的回调函数
  • ArgType.Rest(f): 停止解析,对其后的每一个字符命令启动一个 string -> unit 的回调函数

就是这么简单!回想当年 C 时代,编写命令行参数解析的麻烦啊,呵呵。

CUDA纹理内存的一致性

最近在开发中遇到这样一个问题:先后调用两个CUDA内核,在第一个内核中更新一片全局内存,然后将其绑定到一个纹理内存;进而调用第二个内核,其中使用纹理内存读取第一个内核中更新的全局内存。但是由于纹理内存的机制是缓存,我担心如果反复调用这两个内核,会不会造成纹理读取和全局内存之间的不一致。

通过网上的搜索,这个网页给出了答案。其中提到,在CUDA的编程手册中的3.2.10.4章节中(参考 CUDA Toolkit 4.0 文档)提到了这个问题:

The texture and surface memory is cached and within the same kernel call, the cache is not kept coherent with respect to global memory writes and surface memory writes, so any texture fetch or surface read to an address that has been written to via a global write or a surface write in the same kernel call returns undefined data. In other words, a thread can safely read some texture or surface memory location only if this memory location has been updated by a previous kernel call or memory copy, but not if it has been previously updated by the same thread or another thread from the same kernel call.

纹理内存采用缓存机制以提高读取速度,但是,它并不确保其读取和其绑定的全局内存之间的一致性。通过纹理内存读取在同一内核调用中更新过的全局内存将导致无法预知的结果。换句话说,当纹理所绑定的全局内存是在之前的内核调用中更新的,或者通过之前的内存拷贝操作过的,这时的纹理读取是安全的;反之,如果你在同一次内核调用的线程中对纹理所绑定的全局内存进行了改变,则这时纹理读取是不安全的。

那么,根据这段描述,可以确定,我的做法是可行的,在不同的内核调用中,纹理内存是能够保持一致性的。CUDA 在新的一次调用中,隐藏了一个纹理缓存清除操作。

斯米兰的晚霞

Gallery

This gallery contains 5 photos.

到寇立的时候,天还在下雨。人品好啊,一到了斯米兰群岛,就放晴了。船宿就是爽,白天的游客在下午四点左右都离开了,整个斯米兰安静了许多,我们在船上,看着晚霞,吃着晚餐,聊着天。。。

通过 PInvoke 调用 Intel Fortran 编译的 dll

最近购买了 Intel 的 Fortran 编译器,可以将很多原汁原味的用 Fortran 编写的数学库编译成 dll 来使用。通过 .NET 的 P/Invoke 来调用 Intel Fortran 编写的库既可行,也方便,但其中涉及到一些细节问题,这里简单总结一下。

首先,用 Intel 的 Visual Studio 向导生成的 Fortran 项目,虽然类型是输出 dll,但奇怪的是,用 PE 查看器一看,里面居然没有输出任何符号。经过网上查阅,原来,还必须在源代码里添加一些编译指令才能将其输出为 dll 的符号,例如: Continue reading

“无法创建新的系统分区”问题的解决

今天给老的笔记本重装 Windows 7,结果发现光驱老化,无法读取安装盘,于是决定从 USB 上安装。那了照相机用的 16G SD 卡,插入读卡器,就开始按照网上的方法制作可启动的安装 USB 了。

USB 制作好后,启动也很顺利,可当在磁盘分区的地方遇到了问题了。选择了一个分区欲安装 Windows 7,结果安装程序报告:安装程序无法创建新的系统分区,也无法定位现有的系统分区。网上一搜,不得了,好多人都遇到这个问题,可是解决办法五花八门,叫人眼花缭乱啊,呵呵。

经过尝试,这个问题的症结确实是在 BIOS 的启动顺序中。如果我将 USB HDD 启动放置于系统的 SATA HDD 启动之前,那么在安装程序需要读取硬盘信息的时候,估计这里是个 BUG,它只去读默认的第一个硬盘设备,但这个时候,我们的硬盘设备是 USB HDD,这当然就出问题了。光盘没有这个问题,因为用光盘安装的人多,所以安装程序也测试的多,自然能很好地区别硬盘和光盘。 Continue reading

通过 PythonNet 使用 XAML 混合编程

昨天的文章里,我介绍了如何利用 PythonNet 来从 Python 中调用 WPF 类库。这只是第一步。接下来将面临的挑战是,如何有效的应付复杂多变的 UI 设计呢?XAML 语言是用来解决这个问题的,它将界面设计与编程逻辑分割开来。但是 XAML 在 Visual Studio 中是通过很多工具来自动产生连接代码的。对于我们的纯手工打造的 Python 程序,如何能利用好 XAML 工具呢?

其实,WPF 类库的 Markup 名字空间下提供的 XamlReader 类就提供了动态加载 XAML 语言的功能。但关键是,用 XamlReader 语言加载的 XAML 语言中不能含有事件属性,你不能简单地在 Button 标签中添加 Click=”xxx” 属性。 Continue reading