or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-control.mdcustom-libraries.mdindex.mdintrospection.mdlimiting.md

custom-libraries.mddocs/

0

# Custom Library Support

1

2

Extend threadpoolctl with custom library controllers for additional thread pool libraries not supported out of the box. This capability allows integration of custom or newer thread pool implementations.

3

4

## Capabilities

5

6

### LibController Base Class

7

8

Abstract base class that defines the interface for all library controllers.

9

10

```python { .api }

11

class LibController:

12

"""

13

Abstract base class for individual library controllers.

14

15

Subclasses must define class attributes and implement abstract methods

16

to support a specific thread pool library implementation.

17

18

Class Attributes (required):

19

user_api: str

20

Standardized API name ('blas', 'openmp', or custom)

21

internal_api: str

22

Implementation-specific identifier (unique name)

23

filename_prefixes: tuple[str, ...]

24

Shared library filename prefixes for detection

25

check_symbols: tuple[str, ...] (optional)

26

Symbol names to verify library compatibility

27

"""

28

29

def __init__(self, *, filepath=None, prefix=None, parent=None):

30

"""

31

Initialize library controller (not meant to be overridden).

32

33

Args:

34

filepath: str - Path to shared library

35

prefix: str - Matched filename prefix

36

parent: ThreadpoolController - Parent controller instance

37

"""

38

39

def info(self):

40

"""

41

Return library information dictionary.

42

43

Returns:

44

dict: Library info with standard keys plus any additional attributes

45

"""

46

47

@property

48

def num_threads(self):

49

"""

50

Current thread limit (dynamic property).

51

52

Returns:

53

int: Current maximum thread count

54

"""

55

56

def get_num_threads(self):

57

"""

58

Get current maximum thread count (abstract method).

59

60

Must be implemented by subclasses.

61

62

Returns:

63

int | None: Current thread limit, None if unavailable

64

"""

65

66

def set_num_threads(self, num_threads):

67

"""

68

Set maximum thread count (abstract method).

69

70

Must be implemented by subclasses.

71

72

Args:

73

num_threads: int - New thread limit

74

75

Returns:

76

Any: Implementation-specific return value

77

"""

78

79

def get_version(self):

80

"""

81

Get library version (abstract method).

82

83

Must be implemented by subclasses.

84

85

Returns:

86

str | None: Version string, None if unavailable

87

"""

88

89

def set_additional_attributes(self):

90

"""

91

Set implementation-specific attributes.

92

93

Called during initialization to set custom attributes that will

94

be included in the info() dictionary. Override to add custom fields.

95

"""

96

```

97

98

### Controller Registration

99

100

Register custom controllers with the threadpoolctl system.

101

102

```python { .api }

103

def register(controller):

104

"""

105

Register a new library controller class.

106

107

Adds the controller to the global registry so it will be used

108

during library discovery in future ThreadpoolController instances.

109

110

Args:

111

controller: type[LibController] - LibController subclass to register

112

"""

113

```

114

115

### Creating Custom Controllers

116

117

```python

118

from threadpoolctl import LibController, register

119

import ctypes

120

121

class MyCustomController(LibController):

122

"""Controller for a custom thread pool library."""

123

124

# Required class attributes

125

user_api = "custom" # Or "blas"/"openmp" if it fits those categories

126

internal_api = "mycustomlib"

127

filename_prefixes = ("libmycustom", "mycustomlib")

128

129

# Optional: symbols to check for library compatibility

130

check_symbols = (

131

"mycustom_get_threads",

132

"mycustom_set_threads",

133

"mycustom_get_version"

134

)

135

136

def get_num_threads(self):

137

"""Get current thread count from the library."""

138

get_func = getattr(self.dynlib, "mycustom_get_threads", None)

139

if get_func is not None:

140

return get_func()

141

return None

142

143

def set_num_threads(self, num_threads):

144

"""Set thread count in the library."""

145

set_func = getattr(self.dynlib, "mycustom_set_threads", None)

146

if set_func is not None:

147

return set_func(num_threads)

148

return None

149

150

def get_version(self):

151

"""Get library version string."""

152

version_func = getattr(self.dynlib, "mycustom_get_version", None)

153

if version_func is not None:

154

version_func.restype = ctypes.c_char_p

155

version_bytes = version_func()

156

if version_bytes:

157

return version_bytes.decode('utf-8')

158

return None

159

160

def set_additional_attributes(self):

161

"""Set custom attributes for this library."""

162

# Add any custom attributes that should appear in info()

163

self.custom_attribute = self._get_custom_info()

164

165

def _get_custom_info(self):

166

"""Helper method to get custom library information."""

167

# Implementation-specific logic

168

return "custom_value"

169

170

# Register the controller

171

register(MyCustomController)

172

```

173

174

### Advanced Custom Controller Examples

175

176

#### BLAS-Compatible Controller

177

178

```python

179

from threadpoolctl import LibController, register

180

import ctypes

181

182

class CustomBLASController(LibController):

183

"""Controller for a custom BLAS implementation."""

184

185

user_api = "blas"

186

internal_api = "customblas"

187

filename_prefixes = ("libcustomblas",)

188

check_symbols = (

189

"customblas_get_num_threads",

190

"customblas_set_num_threads",

191

"customblas_get_config"

192

)

193

194

def get_num_threads(self):

195

get_func = getattr(self.dynlib, "customblas_get_num_threads", None)

196

if get_func:

197

threads = get_func()

198

# Handle special return values (like OpenBLAS -1 = sequential)

199

return 1 if threads == -1 else threads

200

return None

201

202

def set_num_threads(self, num_threads):

203

set_func = getattr(self.dynlib, "customblas_set_num_threads", None)

204

if set_func:

205

return set_func(num_threads)

206

return None

207

208

def get_version(self):

209

config_func = getattr(self.dynlib, "customblas_get_config", None)

210

if config_func:

211

config_func.restype = ctypes.c_char_p

212

config = config_func()

213

if config:

214

# Parse version from config string

215

config_str = config.decode('utf-8')

216

# Extract version using custom parsing logic

217

return self._parse_version(config_str)

218

return None

219

220

def set_additional_attributes(self):

221

"""Add BLAS-specific attributes."""

222

self.threading_layer = self._get_threading_layer()

223

self.architecture = self._get_architecture()

224

225

def _parse_version(self, config_str):

226

"""Parse version from config string."""

227

# Custom parsing logic

228

import re

229

match = re.search(r'version\s+(\d+\.\d+\.\d+)', config_str, re.IGNORECASE)

230

return match.group(1) if match else None

231

232

def _get_threading_layer(self):

233

"""Determine threading layer."""

234

# Custom logic to determine threading backend

235

return "openmp" # or "pthreads", "disabled", etc.

236

237

def _get_architecture(self):

238

"""Get target architecture."""

239

# Custom logic to get architecture info

240

return "x86_64"

241

242

register(CustomBLASController)

243

```

244

245

#### Controller with Dynamic Methods

246

247

```python

248

from threadpoolctl import LibController, register

249

import ctypes

250

251

class FlexibleController(LibController):

252

"""Controller that adapts to different library versions."""

253

254

user_api = "custom"

255

internal_api = "flexlib"

256

filename_prefixes = ("libflex",)

257

258

def __init__(self, **kwargs):

259

super().__init__(**kwargs)

260

# Determine available methods during initialization

261

self._detect_capabilities()

262

263

def _detect_capabilities(self):

264

"""Detect which methods are available in this library version."""

265

self.has_v1_api = hasattr(self.dynlib, "flex_get_threads_v1")

266

self.has_v2_api = hasattr(self.dynlib, "flex_get_threads_v2")

267

self.has_version_api = hasattr(self.dynlib, "flex_version")

268

269

def get_num_threads(self):

270

if self.has_v2_api:

271

return self.dynlib.flex_get_threads_v2()

272

elif self.has_v1_api:

273

return self.dynlib.flex_get_threads_v1()

274

return None

275

276

def set_num_threads(self, num_threads):

277

if self.has_v2_api:

278

return self.dynlib.flex_set_threads_v2(num_threads)

279

elif self.has_v1_api:

280

return self.dynlib.flex_set_threads_v1(num_threads)

281

return None

282

283

def get_version(self):

284

if self.has_version_api:

285

version_func = self.dynlib.flex_version

286

version_func.restype = ctypes.c_char_p

287

return version_func().decode('utf-8')

288

return None

289

290

def set_additional_attributes(self):

291

self.api_version = "v2" if self.has_v2_api else ("v1" if self.has_v1_api else "unknown")

292

293

register(FlexibleController)

294

```

295

296

### Using Custom Controllers

297

298

```python

299

from threadpoolctl import ThreadpoolController, threadpool_info

300

301

# After registering custom controllers, they work like built-in ones

302

controller = ThreadpoolController()

303

304

# Custom controllers appear in standard introspection

305

info = threadpool_info()

306

custom_libs = [lib for lib in info if lib['user_api'] == 'custom']

307

print(f"Found {len(custom_libs)} custom thread pool libraries")

308

309

# Custom controllers work with selection and limiting

310

custom_controller = controller.select(internal_api='mycustomlib')

311

if custom_controller:

312

with custom_controller.limit(limits=2):

313

# Custom library limited to 2 threads

314

result = computation_using_custom_library()

315

```

316

317

### Error Handling in Custom Controllers

318

319

```python

320

from threadpoolctl import LibController, register

321

import ctypes

322

323

class RobustController(LibController):

324

"""Controller with comprehensive error handling."""

325

326

user_api = "custom"

327

internal_api = "robustlib"

328

filename_prefixes = ("librobust",)

329

330

def get_num_threads(self):

331

try:

332

get_func = getattr(self.dynlib, "robust_get_threads", None)

333

if get_func:

334

result = get_func()

335

# Validate result

336

if isinstance(result, int) and result > 0:

337

return result

338

except (AttributeError, OSError, ValueError) as e:

339

# Log error but don't crash

340

print(f"Warning: Could not get thread count from {self.internal_api}: {e}")

341

return None

342

343

def set_num_threads(self, num_threads):

344

if not isinstance(num_threads, int) or num_threads < 1:

345

raise ValueError(f"Invalid thread count: {num_threads}")

346

347

try:

348

set_func = getattr(self.dynlib, "robust_set_threads", None)

349

if set_func:

350

return set_func(num_threads)

351

except (AttributeError, OSError) as e:

352

print(f"Warning: Could not set thread count in {self.internal_api}: {e}")

353

return None

354

355

def get_version(self):

356

try:

357

version_func = getattr(self.dynlib, "robust_version", None)

358

if version_func:

359

version_func.restype = ctypes.c_char_p

360

version_bytes = version_func()

361

if version_bytes:

362

return version_bytes.decode('utf-8', errors='ignore')

363

except Exception as e:

364

print(f"Warning: Could not get version from {self.internal_api}: {e}")

365

return None

366

367

register(RobustController)

368

```