source: sasview/src/sas/sasgui/perspectives/fitting/gpu_options.py @ 4688acf

Last change on this file since 4688acf was 4688acf, checked in by Paul Kienzle <pkienzle@…>, 6 months ago

redo GPU dialog to show test progress

  • Property mode set to 100644
File size: 12.2 KB
Line 
1'''
2Module provides dialog for setting SAS_OPENCL variable, which defines
3device choice for OpenCL calculation
4
5Created on Nov 29, 2016
6
7@author: wpotrzebowski
8'''
9
10import json
11import platform
12import logging
13import os
14import sys
15
16import wx
17
18# TODO: move device query functions to sasmodels
19try:
20    import pyopencl as cl
21except ImportError:
22    cl = None
23
24import sasmodels
25import sasmodels.model_test
26import sasmodels.sasview_model
27from sasmodels.generate import F32, F64
28
29from sas.sasgui.guiframe.documentation_window import DocumentationWindow
30
31logger = logging.getLogger(__name__)
32
33class CustomMessageBox(wx.Dialog):
34    """
35    Custom message box for OpenCL results
36    """
37    def __init__(self, parent, msg, title):
38
39        wx.Dialog.__init__(self, parent, title=title)
40
41        self.static_box = wx.StaticBox(self, -1, "OpenCL test completed!")
42        self.boxsizer = wx.BoxSizer(orient=wx.VERTICAL)
43
44        self.text = wx.TextCtrl(self, -1, size=(500, 300),
45                                style=wx.TE_MULTILINE|wx.TE_READONLY)
46        self.text.SetValue(msg)
47        self.text.SetBackgroundColour(self.GetBackgroundColour())
48        self.text.SetFocus()
49        self.text.SetInsertionPoint(self.text.GetLastPosition())
50        self.boxsizer.Add(self.text, proportion=1, flag=wx.EXPAND)
51
52        self.fit_hsizer = wx.StaticBoxSizer(self.static_box, orient=wx.VERTICAL)
53        self.fit_hsizer.Add(self.boxsizer, 0, wx.ALL, 5)
54
55        self.vbox = wx.BoxSizer(wx.VERTICAL)
56        self.vbox.Add(self.fit_hsizer, 0, wx.ALL, 10)
57
58        self.message_text = wx.StaticText(self, -1, "If tests fail on OpenCL devices, "
59                                                    "please select No OpenCL option.\n\n"
60                                                    "In case of large number of failing tests, "
61                                                    "please consider sending\n"
62                                                    "above report to help@sasview.org.")
63
64        self.vbox.Add(self.message_text, 0, wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
65
66        self.ok_btn = wx.Button(self, wx.ID_OK)
67
68        self.btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
69        self.btn_sizer.Add((10, 20), 1) # stretchable whitespace
70        self.btn_sizer.Add(self.ok_btn, 0)
71
72        self.vbox.Add(self.btn_sizer, 0, wx.EXPAND|wx.ALL, 10)
73
74        self.SetSizer(self.vbox)
75        self.vbox.Fit(self)
76
77        self.SetAutoLayout(True)
78
79
80class GpuOptions(wx.Dialog):
81    """
82    "OpenCL options" Dialog Box
83
84    Provides dialog for setting SAS_OPENCL variable, which defines
85    device choice for OpenCL calculation
86
87    """
88
89    def __init__(self, *args, **kwds):
90
91        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
92        wx.Dialog.__init__(self, *args, **kwds)
93
94        clinfo = self._get_clinfo()
95
96        self.panel1 = wx.Panel(self, -1)
97        static_box1 = wx.StaticBox(self.panel1, -1, "Available OpenCL Options:")
98
99        boxsizer = wx.BoxSizer(orient=wx.VERTICAL)
100        self.option_button = {}
101        self.buttons = []
102        #Check if SAS_OPENCL is already set as environment variable
103        self.sas_opencl = os.environ.get("SAS_OPENCL", "")
104
105        for clopt in clinfo:
106            button = wx.CheckBox(self.panel1, -1, label=clopt[1], name=clopt[1])
107
108            if clopt != "No OpenCL":
109                self.option_button[clopt[1]] = clopt[0]
110                if self.sas_opencl == clopt[0]:
111                    button.SetValue(1)
112            else:
113                self.option_button[clopt] = "None"
114                if self.sas_opencl.lower() == "none":
115                    button.SetValue(1)
116
117            self.Bind(wx.EVT_CHECKBOX, self.on_check, id=button.GetId())
118            self.buttons.append(button)
119            boxsizer.Add(button, 0, 0)
120
121        fit_hsizer = wx.StaticBoxSizer(static_box1, orient=wx.VERTICAL)
122        fit_hsizer.Add(boxsizer, 0, wx.ALL, 5)
123
124        self.panel1.SetSizer(fit_hsizer)
125
126        self.vbox = wx.BoxSizer(wx.VERTICAL)
127        self.vbox.Add(self.panel1, 0, wx.ALL, 10)
128
129        accept_btn = wx.Button(self, wx.ID_OK)
130        accept_btn.SetToolTipString("Accept new OpenCL settings. This will"
131                                    " override SAS_OPENCL variable if set")
132
133        help_id = wx.NewId()
134        help_btn = wx.Button(self, help_id, 'Help')
135        help_btn.SetToolTipString("Help on the GPU options")
136
137        reset_id = wx.NewId()
138        reset_btn = wx.Button(self, reset_id, 'Reset')
139        reset_btn.SetToolTipString("Restore initial settings")
140
141        test_id = wx.NewId()
142        test_btn = wx.Button(self, test_id, 'Test')
143        test_btn.SetToolTipString("Test if models compile on the given infrastructure")
144        self.test_btn = test_btn
145
146        self.Bind(wx.EVT_BUTTON, self.on_OK, accept_btn)
147        self.Bind(wx.EVT_BUTTON, self.on_test, test_btn)
148        self.Bind(wx.EVT_BUTTON, self.on_reset, reset_btn)
149        self.Bind(wx.EVT_BUTTON, self.on_help, help_btn)
150
151        test_text = wx.StaticText(self, -1, "WARNING: Running tests can take a few minutes!")
152        test_text2 = wx.StaticText(self, -1, "NOTE: No test will run if No OpenCL is checked")
153        test_text.SetForegroundColour(wx.RED)
154        self.vbox.Add(test_text2, 0, wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
155        self.vbox.Add(test_text, 0, wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
156
157        btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
158        btn_sizer.Add((10, 20), 1) # stretchable whitespace
159        btn_sizer.Add(accept_btn, 0)
160        btn_sizer.Add((10, 20), 0) # non-stretchable whitespace
161        btn_sizer.Add(test_btn, 0)
162        btn_sizer.Add((10, 20), 0) # non-stretchable whitespace
163        btn_sizer.Add(reset_btn, 0)
164        btn_sizer.Add((10, 20), 0) # non-stretchable whitespace
165        btn_sizer.Add(help_btn, 0)
166
167        # Add the button sizer to the main sizer.
168        self.vbox.Add(btn_sizer, 0, wx.EXPAND|wx.ALL, 10)
169
170        self.SetSizer(self.vbox)
171        self.vbox.Fit(self)
172        self.SetTitle("OpenCL options")
173        self.Centre()
174
175    def _get_clinfo(self):
176        """
177        Reading in information about available OpenCL infrastructure
178        :return:
179        """
180        # TODO: Include cuda platforms if available.
181        clinfo = []
182        platforms = []
183
184        if cl is None:
185            logger.warn("Unable to import the pyopencl package.  It may not "
186                        "have been installed.  If you wish to use OpenCL, try "
187                        "running pip install --user pyopencl")
188        else:
189            try:
190                platforms = cl.get_platforms()
191            except cl.LogicError as err:
192                logger.warn("Unable to fetch the OpenCL platforms.  This likely "
193                            "means that the opencl drivers for your system are "
194                            "not installed.")
195                logger.warn(err)
196
197        p_index = 0
198        for platform in platforms:
199            d_index = 0
200            devices = platform.get_devices()
201            for device in devices:
202                if len(devices) > 1 and len(platforms) > 1:
203                    combined_index = ":".join([str(p_index), str(d_index)])
204                elif len(platforms) > 1:
205                    combined_index = str(p_index)
206                else:
207                    combined_index = str(d_index)
208                clinfo.append((combined_index, ":".join([platform.name, device.name])))
209                d_index += 1
210            p_index += 1
211
212        clinfo.append(("None", "No OpenCL"))
213        return clinfo
214
215    def on_check(self, event):
216        """
217        Action triggered when box is selected
218        :param event:
219        :return:
220        """
221        selected_button = event.GetEventObject()
222        for btn in self.buttons:
223            if btn != selected_button:
224                btn.SetValue(0)
225        if selected_button.GetValue():
226            self.sas_opencl = self.option_button[selected_button.Name]
227        else:
228            self.sas_opencl = None
229
230    def on_OK(self, event):
231        """
232        Close window on accpetance
233        """
234
235        #If statement added to handle Reset
236        if self.sas_opencl:
237            os.environ["SAS_OPENCL"] = self.sas_opencl
238        else:
239            if "SAS_OPENCL" in os.environ:
240                del os.environ["SAS_OPENCL"]
241        sasmodels.sasview_model.reset_environment()
242        event.Skip()
243
244    def on_reset(self, event):
245        """
246        Resets selected values
247        """
248        for btn in self.buttons:
249            btn.SetValue(0)
250
251        self.sas_opencl = None
252
253    def on_test(self, event):
254        """
255        Run sasmodels check from here and report results from
256        """
257        #The same block of code as for OK but it is needed if we want to have
258        #active response to Test button
259
260        no_opencl_msg = False
261        if self.sas_opencl:
262            os.environ["SAS_OPENCL"] = self.sas_opencl
263            if self.sas_opencl.lower() == "none":
264                no_opencl_msg = True
265        else:
266            if "SAS_OPENCL" in os.environ:
267                del os.environ["SAS_OPENCL"]
268        # CRUFT: next version of reset_environment() will return env
269        env = sasmodels.sasview_model.reset_environment()
270
271        try:
272            env = sasmodels.kernelcl.environment()
273            clinfo = {}
274            if env.context[F64] is None:
275                clinfo['double'] = "None"
276            else:
277                ctx64 = env.context[F64].devices[0]
278                clinfo['double'] = ", ".join((
279                    ctx64.platform.vendor,
280                    ctx64.platform.version,
281                    ctx64.vendor,
282                    ctx64.name,
283                    ctx64.version))
284            if env.context[F32] is None:
285                clinfo['single'] = "None"
286            else:
287                ctx32 = env.context[F32].devices[0]
288                clinfo['single'] = ", ".join((
289                    ctx32.platform.vendor,
290                    ctx32.platform.version,
291                    ctx32.vendor,
292                    ctx32.name,
293                    ctx32.version))
294            # If the same device is used for single and double precision, then
295            # say so. Whether double is the same as single or single is the
296            # same as double depends on the order they are listed below.
297            if env.context[F32] == env.context[F64]:
298                clinfo['double'] = "same as single precision"
299        except Exception as exc:
300            logger.debug("exc %s", str(exc))
301            clinfo = {'double': "None", 'single': "None"}
302
303        msg = "\nPlatform Details:\n\n"
304        msg += "Sasmodels version: "
305        msg += sasmodels.__version__ + "\n"
306        msg += "\nPlatform used: "
307        msg += json.dumps(platform.uname()) + "\n"
308        if no_opencl_msg:
309            msg += "\nOpenCL driver: None\n"
310        else:
311            msg += "\nOpenCL driver:\n"
312            msg += "   single precision: " + clinfo['single'] + "\n"
313            msg += "   double precision: " + clinfo['double'] + "\n"
314
315        msg_title = 'OpenCL tests results'
316        running = msg + "\nRunning tests.  This may take several minutes.\n\n"
317        msg_dialog = CustomMessageBox(self.panel1, running, msg_title)
318        msg_dialog.Show()
319
320        failures = []
321        tests_completed = 0
322        self.test_btn.Disable()
323        tests = sasmodels.model_test.make_suite('opencl', ['all'])
324        for test in tests:
325            try:
326                wx.Yield()
327                test.run_all()
328                msg_dialog.text.AppendText('.')
329            except Exception as exc:
330                logger.debug("%s failed with %s", test.test_name, str(exc))
331                msg_dialog.text.AppendText('\nFail: ' + test.test_name)
332                failures.append(test.test_name)
333            tests_completed += 1
334            # TODO: Put a stop button in CustomDialog and test it here.
335            #if tests_completed > 5: break
336
337        status = 'Failed %d of %d' % (len(failures), tests_completed)
338        msg_dialog.text.AppendText('\n\n' + status + '\n')
339        self.test_btn.Enable()
340        msg_dialog.ShowModal()
341        msg_dialog.Destroy()
342
343    def on_help(self, event):
344        """
345        Provide help on opencl options.
346        """
347        TreeLocation = "user/sasgui/perspectives/fitting/gpu_setup.html"
348        anchor = "#device-selection"
349        DocumentationWindow(self, -1,
350                            TreeLocation, anchor, "OpenCL Options Help")
Note: See TracBrowser for help on using the repository browser.