@@ -170,6 +170,8 @@ use_backend <- function(backend, gpu = NA) {
170170 reticulate :: import(" os" )$ environ $ update(list (KERAS_BACKEND = backend ))
171171 }
172172
173+ set_envvar(" UV_CONSTRAINT" , pkg_file(" keras-constraints.txt" ),
174+ action = " append" , sep = " " , unique = TRUE )
173175
174176 switch (
175177 paste0(get_os(), " _" , backend ),
@@ -222,18 +224,19 @@ use_backend <- function(backend, gpu = NA) {
222224 gpu <- has_gpu()
223225
224226 if (gpu ) {
227+ uv_unset_override_tf_cpu()
225228 py_require(action = " remove" , c(" tensorflow" , " tensorflow-cpu" ))
226229 py_require(" tensorflow[and-cuda]" )
227230 } else {
228- py_require(action = " remove" , c(" tensorflow" , " tensorflow[and-cuda]" ))
229- py_require(" tensorflow-cpu" )
231+ uv_set_override_tf_cpu()
230232 }
231233 },
232234
233235 Linux_jax = {
234236 py_require(action = " remove" ,
235237 c(" tensorflow" , " tensorflow[and-cuda]" ,
236238 " jax[cuda12]" , " jax[cpu]" ))
239+ uv_set_override_tf_cpu()
237240
238241 if (is.na(gpu ))
239242 gpu <- has_gpu()
@@ -248,6 +251,7 @@ use_backend <- function(backend, gpu = NA) {
248251
249252 Linux_torch = {
250253 py_require(c(" tensorflow" , " tensorflow[and-cuda]" ), action = " remove" )
254+ uv_set_override_tf_cpu()
251255
252256 if (is.na(gpu ))
253257 gpu <- has_gpu()
@@ -264,6 +268,7 @@ use_backend <- function(backend, gpu = NA) {
264268 },
265269
266270 Linux_numpy = {
271+ uv_set_override_tf_cpu()
267272 py_require(c(" tensorflow" , " tensorflow[and-cuda]" ), action = " remove" )
268273 py_require(c(" tensorflow-cpu" , " numpy" , " jax[cpu]" ))
269274 },
@@ -301,8 +306,62 @@ use_backend <- function(backend, gpu = NA) {
301306 invisible (backend )
302307}
303308
309+ set_envvar <- function (
310+ name ,
311+ value ,
312+ action = c(" replace" , " append" , " prepend" ),
313+ sep = .Platform $ path.sep ,
314+ unique = FALSE
315+ ) {
316+ old <- Sys.getenv(name , NA )
317+
318+ if (is.null(value ) || is.na(value )) {
319+ Sys.unsetenv(name )
320+ return (invisible (old ))
321+ }
322+
323+ if (! is.na(old )) {
324+ value <- switch (
325+ match.arg(action ),
326+ replace = value ,
327+ append = paste(old , value , sep = sep ),
328+ prepend = paste(value , old , sep = sep )
329+ )
330+ if (unique ) {
331+ value <- unique(unlist(strsplit(value , sep , fixed = TRUE )))
332+ value <- paste0(value , collapse = sep )
333+ }
334+ }
335+
336+ value <- list (value )
337+ names(value ) <- name
338+ do.call(Sys.setenv , value )
339+ invisible (old )
340+ }
304341
342+ uv_set_override_tf_cpu <- function () {
343+ py_require(action = " remove" , c(
344+ " tensorflow" , " tensorflow[and-cuda]" , " tensorflow-cpu" ,
345+ " tensorflow-metal" , " tensorflow-macos"
346+ ))
347+ py_require(if (is_linux()) " tensorflow-cpu" else " tensorflow" )
348+ set_envvar(" UV_OVERRIDE" , pkg_file(" tf-cpu-override.txt" ),
349+ action = " append" , sep = " " , unique = TRUE )
350+ }
305351
352+ uv_unset_override_tf_cpu <- function () {
353+ override <- Sys.getenv(" UV_OVERRIDE" , NA )
354+ if (is.na(override )) return ()
355+ cpu_override <- pkg_file(" tf-cpu-override.txt" )
356+ if (override == cpu_override ) {
357+ Sys.unsetenv(override )
358+ } else {
359+ new <- gsub(cpu_override , " " , override , fixed = TRUE )
360+ new <- gsub(" +" , " " , new )
361+ Sys.setenv(" UV_OVERRIDE" = new )
362+ }
363+ invisible (override )
364+ }
306365
307366get_os <- function () {
308367 if (is_windows()) " Windows" else if (is_mac_arm64()) " macOS" else " Linux"
@@ -321,6 +380,13 @@ is_keras_loaded <- function() {
321380 ! exists(" module" , envir = keras )
322381}
323382
383+ pkg_file <- function (... , package = " keras3" ) {
384+ path <- system.file(... , package = " keras3" , mustWork = TRUE )
385+ if (is_windows())
386+ path <- utils :: shortPathName(path )
387+ path
388+ }
389+
324390
325391has_gpu <- function () {
326392
0 commit comments