5555// - "allocator" (string, default="default"): which allocator to use. For CUDA the available ones are "default"
5656// (== "bfc"), "bfc" ("best-fit for coalescing", avoids framementation), "cuda_async" (dynamic, no preallocation),
5757// "platform" (slow, good for debugging), "vmm"
58+ // - "visible_devices" (list of integers, e.g., "0;1;2"): list IDs of the devices made visible to the backend.
59+ // - "use_tfrt_gpu_client" (boolean, default=false): uses the "TFRT" dispatcher for GPU.
5860//
5961// # (NO) Dynamic Shapes
6062//
@@ -113,6 +115,20 @@ func New(config string) (compute.Backend, error) {
113115 return NewWithOptions (config , nil )
114116}
115117
118+ // optionsDocumentation contains the help text for the options.
119+ const optionsDocumentation = `"xla" backend extra options:
120+ - "tf32" (boolean, default=true): controls whether to use TF32 for DotGeneral operations that are using float32
121+ (it can be faster in modern GPUs). It's enabled by default.
122+ - "shared_buffer" (boolean, default=true): controls whether to use shared buffers for the device buffer
123+ (where device=CPU). It's enabled by default if the plugin is called "cpu".
124+ - "preallocate" (boolean, default=true): whether the CUDA PJRT preallocates a large portion of the memory.
125+ - "memory_fraction" (float, default=0.75): how much memory to preallocate.
126+ - "allocator" (string, default="default"): which allocator to use. For CUDA the available ones are "default"
127+ (== "bfc"), "bfc" ("best-fit for coalescing", avoids framementation), "cuda_async" (dynamic, no preallocation),
128+ "platform" (slow, good for debugging), "vmm"
129+ - "visible_devices" (list of integers, e.g., "0;1;2"): list IDs of the devices made visible to the backend.
130+ - "use_tfrt_gpu_client" (boolean, default=false): uses the "TFRT" dispatcher for GPU.`
131+
116132// NewWithOptions creates a StableHLO backend with the given client options.
117133// It allows more control, not available with the default New constructor.
118134//
@@ -144,6 +160,13 @@ func NewWithOptions(config string, options pjrt.NamedValuesMap) (*Backend, error
144160 }
145161 }
146162
163+ _ , helpOptionSet := backendOptions ["help" ]
164+ if pluginName == "help" || helpOptionSet {
165+ klog .Infof ("Available plugins: %q" , GetAvailablePlugins ())
166+ klog .Info (optionsDocumentation )
167+ return nil , errors .New ("Help requested" )
168+ }
169+
147170 // FInd plugin.
148171 if ! filepath .IsAbs (pluginName ) {
149172 if autoInstall {
@@ -186,55 +209,57 @@ func NewWithOptions(config string, options pjrt.NamedValuesMap) (*Backend, error
186209 }
187210
188211 // Support "shared buffers":
189- const shareBuffersName = "shared_buffers"
190- if val := backendOptions [sharedBuffersName ]; val != "" {
191- b , err := strconv .ParseBool (val )
192- if err != nil {
193- return nil , errors .Wrapf (err , "Failed to parse option %q=%q" , sharedBuffersName , val )
194- }
212+ if b , found , err := parseOptions [bool ]("shared_buffers" , backendOptions ); err != nil {
213+ return nil , err
214+ } else if found {
195215 backend .hasSharedBuffers = b
196- delete (backendOptions , shareBuffersName )
197- } else if _ , ok := backendOptions ["noshared_buffers" ]; ok {
198- backend .hasSharedBuffers = false
199- delete (backendOptions , "noshared_buffers" )
200216 }
201217
202218 // Support for tf32 DotGeneral.
203- const tf32Name = "tf32"
204- if val := backendOptions [tf32Name ]; val != "" {
205- backend .DotGeneralUseTF32 = true
206- delete (backendOptions , "tf32" )
207- } else if _ , ok := backendOptions ["notf32" ]; ok {
208- // Deprecated, but still supported.
209- backend .DotGeneralUseTF32 = false
210- delete (backendOptions , "notf32" )
219+ if b , found , err := parseOptions [bool ]("tf32" , backendOptions ); err != nil {
220+ return nil , err
221+ } else if found {
222+ backend .DotGeneralUseTF32 = b
211223 }
212224
213225 // Support "preallocate":
214- const preallocateName = "preallocate"
215- if val := backendOptions [preallocateName ]; val != "" {
216- b , err := strconv .ParseBool (val )
217- if err != nil {
218- return nil , errors .Wrapf (err , "Failed to parse option %q=%q" , preallocateName , val )
219- }
220- pluginOptions [preallocateName ] = b
221- delete (backendOptions , preallocateName )
226+ if b , found , err := parseOptions [bool ]("preallocate" , backendOptions ); err != nil {
227+ return nil , err
228+ } else if found {
229+ pluginOptions ["preallocate" ] = b
222230 }
223231
224232 // Control memory fraction of preallocated memory:
225- const memoryFractionName = "memory_fraction"
226- if val := backendOptions [memoryFractionName ]; val != "" {
227- f , err := strconv .ParseFloat (val , 32 )
228- if err != nil {
229- return nil , errors .Wrapf (err , "Failed to parse option %q=%q" , memoryFractionName , val )
230- }
231- pluginOptions [memoryFractionName ] = float32 (f )
232- delete (backendOptions , memoryFractionName )
233+ if f , found , err := parseOptions [float32 ]("memory_fraction" , backendOptions ); err != nil {
234+ return nil , err
235+ } else if found {
236+ pluginOptions ["memory_fraction" ] = f
237+ }
238+
239+ // Allocator to use for CUDA:
240+ if s , found , err := parseOptions [string ]("allocator" , backendOptions ); err != nil {
241+ return nil , err
242+ } else if found {
243+ pluginOptions ["allocator" ] = s
244+ }
245+
246+ // Visible devices for the client:
247+ if visibleDevices , found , err := parseOptions [[]int64 ]("visible_devices" , backendOptions ); err != nil {
248+ return nil , err
249+ } else if found {
250+ pluginOptions ["visible_devices" ] = visibleDevices
251+ }
252+
253+ // Use TFRT GPU client:
254+ if useTFRT , found , err := parseOptions [bool ]("use_tfrt_gpu_client" , backendOptions ); err != nil {
255+ return nil , err
256+ } else if found {
257+ pluginOptions ["use_tfrt_gpu_client" ] = useTFRT
233258 }
234259
235260 // Any leftover plugin options are unknown.
236261 if len (backendOptions ) != 0 {
237- klog .Errorf ("backend %q: unknown plugin options %v" , BackendName , backendOptions )
262+ klog .Errorf ("backend %q: unknown plugin options %v" , BackendName , xslices . SortedKeys ( backendOptions ) )
238263 }
239264
240265 // Create plugin.
@@ -257,19 +282,66 @@ func NewWithOptions(config string, options pjrt.NamedValuesMap) (*Backend, error
257282 return backend , nil
258283}
259284
260- // parseOption parses the optionName from backendOptions (string).
285+ // parseOptions parses the optionName from backendOptions (string).
261286// If optionName is found, it's removed from backendOptions.
262- // It returns the parsed value and whether it was found.
263- func parseOption [T interface { string | bool | float32 }](
264- optionName string , backendOptions map [string ]string ) (T , bool ) {
287+ // For bool options, it also searches for "no"+optionName, and if found, removes it and returns false.
288+ // It returns the parsed value, whether it was found, and any parsing error.
289+ func parseOptions [T interface {
290+ string | bool | float32 | []int64
291+ }](
292+ optionName string , backendOptions map [string ]string ) (T , bool , error ) {
265293 var val T
266- valStr := backendOptions [optionName ]
267- if valStr == "" {
268- return val , false
294+
295+ if _ , ok := any (val ).(bool ); ok {
296+ noKey := "no" + optionName
297+ if _ , foundNo := backendOptions [noKey ]; foundNo {
298+ delete (backendOptions , noKey )
299+ return any (false ).(T ), true , nil
300+ }
301+ }
302+
303+ valStr , found := backendOptions [optionName ]
304+ if ! found {
305+ return val , false , nil
269306 }
270307 delete (backendOptions , optionName )
271- switch any (val ).type {
272-
308+
309+ switch any (val ).(type ) {
310+ case string :
311+ return any (valStr ).(T ), true , nil
312+ case bool :
313+ if valStr == "" {
314+ return any (true ).(T ), true , nil
315+ }
316+ b , err := strconv .ParseBool (valStr )
317+ if err != nil {
318+ return val , true , errors .Wrapf (err , "Failed to parse option %q=%q" , optionName , valStr )
319+ }
320+ return any (b ).(T ), true , nil
321+ case float32 :
322+ f , err := strconv .ParseFloat (valStr , 32 )
323+ if err != nil {
324+ return val , true , errors .Wrapf (err , "Failed to parse option %q=%q" , optionName , valStr )
325+ }
326+ return any (float32 (f )).(T ), true , nil
327+ case []int64 :
328+ if valStr == "" {
329+ return val , true , nil
330+ }
331+ parts := strings .FieldsFunc (valStr , func (r rune ) bool {
332+ return r == ';' || r == ':' || r == ' '
333+ })
334+ res := make ([]int64 , 0 , len (parts ))
335+ for _ , part := range parts {
336+ valInt , err := strconv .ParseInt (part , 10 , 64 )
337+ if err != nil {
338+ return val , true , errors .Wrapf (err , "Failed to parse option %q=%q" , optionName , valStr )
339+ }
340+ res = append (res , valInt )
341+ }
342+ return any (res ).(T ), true , nil
343+ default :
344+ panic ("unreachable" )
273345 }
274346}
275347
0 commit comments