diff --git a/DESCRIPTION b/DESCRIPTION index 80e8a59..0b37fcd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -3,7 +3,8 @@ Title: Minimal Deep Learning Model Implementations Version: 0.0.0.9000 Authors@R: c( person("Daniel", "Falbel", , "daniel@posit.co", role = c("aut", "cre")), - person(family = "Posit", role = c("cph")) + person(family = "Posit", role = c("cph")), + person("Christophe", "Regouby", role = c("ctb")) ) Description: A collection of minimal implementations of deep learning models. Clean and readable code prioritizing simplicity and understanding diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..2f81007 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,4 @@ +# minhub (development version) + +* Add support for `gpt2`, `gptneox`, `gptbigcode` and `llama`. +* Add message translation to french (#3 @cregouby). diff --git a/R/gpt2.R b/R/gpt2.R index 0f9a526..45d11e9 100644 --- a/R/gpt2.R +++ b/R/gpt2.R @@ -197,21 +197,25 @@ gpt2_from_config <- function(identifier, revision = "main") { config <- jsonlite::fromJSON(path) if (config$model_type != "gpt2") - cli::cli_abort(c( - "{.arg config$model_type} must be {.val gpt2}, got {.val {config$model_type}}" + cli::cli_abort(gettext( + "{.arg config$model_type} must be {.val gpt2}, got {.val {config$model_type}}", + domain = "R-minhub" )) if (config$layer_norm_eps != 1e-5) - cli::cli_abort("{.arg config$layer_norm_eps} must be {.val 1e-5}.") + cli::cli_abort(gettext("{.arg config$layer_norm_eps} must be {.val 1e-5}.", + domain = "R-minhub")) pdrop <- unlist(config[c("resid_pdrop", "embd_pdrop", "attn_pdrop")]) if (length(unique(pdrop)) != 1) - cli::cli_abort("{.arg {names(pdrop)}} must be all equal, but got {pdrop}") + cli::cli_abort(gettext("{.arg {names(pdrop)}} must be all equal, but got {pdrop}", + domain = "R-minhub")) else pdrop <- unique(pdrop) if (config$initializer_range != 0.02) - cli::cli_abort("{.arg initializer_range} must be {.val 0.02}, got {config$initializer_range}") + cli::cli_abort(gettext("{.arg initializer_range} must be {.val 0.02}, got {config$initializer_range}", + domain = "R-minhub")) vocab_size <- config$vocab_size n_embd <- config$n_embd diff --git a/R/gptbigcode.R b/R/gptbigcode.R index 23a8f2d..030bff7 100644 --- a/R/gptbigcode.R +++ b/R/gptbigcode.R @@ -135,27 +135,31 @@ gptbigcode_from_config <- function(identifier, revision = "main") { config <- jsonlite::fromJSON(path) if (config$model_type != "gpt_bigcode") - cli::cli_abort(c( + cli::cli_abort(gettext( x = "{.arg config$model_type} must be {.val gpt_bigcode}.", - i = "Got {.val {config$model_type}}" + i = "Got {.val {config$model_type}}", + domain = "R-minhub" )) if (!config$multi_query) - cli::cli_abort("Must use {.arg config$multi_query} but got {.val FALSE}") + cli::cli_abort(gettext("Must use {.arg config$multi_query} but got {.val FALSE}", + domain = "R-minhub")) dropouts <- config[c("attn_pdrop", "resid_pdrop", "embd_pdrop")] if (length(unique(dropouts)) != 1) - cli::cli_abort(c( + cli::cli_abort(gettext( x = "All dropout must be equal.", - i = "Got {.val {names(dropouts)}} respectively {.val {dropouts}}" + i = "Got {.val {names(dropouts)}} respectively {.val {dropouts}}", + domain = "R-minhub" )) else pdrop <- unique(dropouts) if (config$layer_norm_eps != 1e-5) - cli::cli_abort(c( - x = "{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}" + cli::cli_abort(gettext( + x = "{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}", + domain = "R-minhub" )) # remap HF config attributes to minhub configurations diff --git a/R/gptneox.R b/R/gptneox.R index 02d28f1..f613df8 100644 --- a/R/gptneox.R +++ b/R/gptneox.R @@ -93,7 +93,7 @@ nn_gptneox_attention <- nn_module( att <- torch_matmul(q, k$transpose(-2, -1)) * (1 / sqrt(k$size(-1))) att <- att$masked_fill(self$bias[,,1:t, 1:t] == 0, self$masked_bias) att <- nnf_softmax(att, dim=-1)$to(dtype = v$dtype) - + y <- torch_matmul(att, v)$transpose(2, 3)$contiguous()$view(c(b, t, h)) self$c_proj(y) } @@ -178,32 +178,37 @@ gptneox_from_config <- function(identifier, revision = "main") { config <- jsonlite::fromJSON(path) if (config$model_type != "gpt_neox") - cli::cli_abort(c( - "{.arg config$model_type} must be {.val gpt_neox}, got {.val {config$model_type}}" + cli::cli_abort(gettext( + "{.arg config$model_type} must be {.val gpt_neox}, got {.val {config$model_type}}", + domain = "R-minhub" )) # parallel residual is not supported if (!config$use_parallel_residual) - cli::cli_abort(c( + cli::cli_abort(gettext( x = "Non parallel residual is not supported.", - i = "{.arg config$use_parallel_residual} is {.val FALSE}" + i = "{.arg config$use_parallel_residual} is {.val FALSE}", + domain = "R-minhub" )) if (config$hidden_act != "gelu") - cli::cli_abort(c( + cli::cli_abort(gettext( x = "Unsupported {.arg config$hidden_act}: {.val {config$hidden_act}}", - i = "Currently only {.val gelu} is supported." + i = "Currently only {.val gelu} is supported.", + domain = "R-minhub" )) if ((config$intermediate_size / config$hidden_size) != 4) - cli::cli_abort(c( + cli::cli_abort(gettext( x = "{.arg config$intermediate_size} must be 4*{.arg config$hidden_size}", - i = "Got {.val {config$intermediate_size}} and {.val {config$hidden_size}}" + i = "Got {.val {config$intermediate_size}} and {.val {config$hidden_size}}", + domain = "R-minhub" )) if (config$layer_norm_eps != 1e-5) - cli::cli_abort(c( - x = "{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}" + cli::cli_abort(gettext( + x = "{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}", + domain = "R-minhub" )) # remap HF config attributes to minhub configurations diff --git a/R/llama.R b/R/llama.R index 41a894a..927661b 100644 --- a/R/llama.R +++ b/R/llama.R @@ -205,14 +205,16 @@ llama_from_config <- function(identifier, revision = "main") { config <- jsonlite::fromJSON(path) if (config$model_type != "llama") - cli::cli_abort(c( - "{.arg config$model_type} must be {.val llama}, got {.val {config$model_type}}" + cli::cli_abort(gettext( + "{.arg config$model_type} must be {.val llama}, got {.val {config$model_type}}", + domain = "R-minhub" )) if (config$hidden_act != "silu") - cli::cli_abort(c( + cli::cli_abort(gettext( x = "Unsupported {.arg config$hidden_act}: {.val {config$hidden_act}}", - i = "Currently only {.val silu} is supported." + i = "Currently only {.val silu} is supported.", + domain = "R-minhub" )) # remap HF config attributes to minhub configurations diff --git a/R/weights.R b/R/weights.R index 2d40cfe..ad05641 100644 --- a/R/weights.R +++ b/R/weights.R @@ -29,9 +29,10 @@ hf_state_dict <- function(identifier, revision = "main") { index_path <- tryCatch({ hub_download(identifier, WEIGHTS_INDEX_NAME(), revision = revision) }, error = function(e) { - cli::cli_abort(c( + cli::cli_abort(gettext( x = "Error downloading weights from {.val {c(WEIGHTS_NAME(), WEIGHTS_INDEX_NAME())}}", - i = "Traceback below shows the error when trying to download {.val {WEIGHTS_NAME()}}" + i = "Traceback below shows the error when trying to download {.val {WEIGHTS_NAME()}}", + domain = "R-minhub" ), parent = err) }) @@ -70,7 +71,9 @@ state_dict_safetensors <- function(identifier, revision) { ) if (inherits(index_path, "try-error")) { - cli::cli_abort("No safetensors files found.") + cli::cli_abort(gettext( + "No safetensors files found.", + domain = "R-minhub")) } index <- jsonlite::fromJSON(index_path)$weight_map %>% diff --git a/inst/po/fr/LC_MESSAGES/R-minhub.mo b/inst/po/fr/LC_MESSAGES/R-minhub.mo new file mode 100644 index 0000000..f8b71fe Binary files /dev/null and b/inst/po/fr/LC_MESSAGES/R-minhub.mo differ diff --git a/po/R-fr.po b/po/R-fr.po new file mode 100644 index 0000000..876e174 --- /dev/null +++ b/po/R-fr.po @@ -0,0 +1,129 @@ +msgid "" +msgstr "" +"Project-Id-Version: minhub 0.0.0.9000\n" +"POT-Creation-Date: 2024-11-28 11:28+0100\n" +"PO-Revision-Date: 2024-12-02 17:11+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" + +#: gpt2.R:201 +msgid "" +"{.arg config$model_type} must be {.val gpt2}, got {.val {config$model_type}}" +msgstr "" +"La valeur de {.arg config$model_type} doit être {.val gpt2}, or elle est à {." +"val {config$model_type}}" + +#: gpt2.R:205 +msgid "{.arg config$layer_norm_eps} must be {.val 1e-5}." +msgstr "{.arg config$layer_norm_eps} doit être à {.val 1e-5}." + +#: gpt2.R:209 +msgid "{.arg {names(pdrop)}} must be all equal, but got {pdrop}" +msgstr "Les {.arg {names(pdrop)}} doivent être égaux, or ils sont à {pdrop}" + +#: gpt2.R:214 +msgid "" +"{.arg initializer_range} must be {.val 0.02}, got {config$initializer_range}" +msgstr "" +"La valeur de {.arg initializer_range} doit être à {.val 0.02}, or elle est à " +"{config$initializer_range}" + +#: gptbigcode.R:139 +msgid "{.arg config$model_type} must be {.val gpt_bigcode}." +msgstr "{.arg config$model_type} doit être {.val gpt_bigcode}." + +#: gptbigcode.R:140 +msgid "Got {.val {config$model_type}}" +msgstr "Or elle est à {.val {config$model_type}}" + +#: gptbigcode.R:144 +msgid "Must use {.arg config$multi_query} but got {.val FALSE}" +msgstr "" +"Il faut utiliser {.arg config$multi_query}, or c'est actuellement {.val " +"FALSE}" + +#: gptbigcode.R:149 +msgid "All dropout must be equal." +msgstr "Tous les {.val dropout} doivent être égaux." + +#: gptbigcode.R:150 +msgid "Got {.val {names(dropouts)}} respectively {.val {dropouts}}" +msgstr "Or {.val {names(dropouts)}} sont respectivement à {.val {dropouts}}" + +#: gptbigcode.R:158 gptneox.R:206 +msgid "" +"{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}" +msgstr "" +"La valeur de {.arg config$layer_norm_eps} doit être {.val 1e-5}, or elle est " +"à {.val {config$layer_norm_eps}}" + +#: gptneox.R:182 +msgid "" +"{.arg config$model_type} must be {.val gpt_neox}, got {.val " +"{config$model_type}}" +msgstr "" +"{.arg config$model_type} doit être {.val gpt_neox}, or elle est à {.val " +"{config$model_type}}" + +#: gptneox.R:188 +msgid "Non parallel residual is not supported." +msgstr "Les résidus non-parallèles ne sont pas pris en compte." + +#: gptneox.R:189 +msgid "{.arg config$use_parallel_residual} is {.val FALSE}" +msgstr "{.arg config$use_parallel_residual} est {.val FALSE}" + +#: gptneox.R:194 llama.R:214 +msgid "Unsupported {.arg config$hidden_act}: {.val {config$hidden_act}}" +msgstr "{.arg config$hidden_act} ne peut pas être {.val {config$hidden_act}}" + +#: gptneox.R:195 +msgid "Currently only {.val gelu} is supported." +msgstr "Actuellement seul {.val gelu} est pris en compte." + +#: gptneox.R:200 +msgid "{.arg config$intermediate_size} must be 4*{.arg config$hidden_size}" +msgstr "" +"{.arg config$intermediate_size} doit être de 4*{.arg config$hidden_size}" + +#: gptneox.R:201 +msgid "Got {.val {config$intermediate_size}} and {.val {config$hidden_size}}" +msgstr "" +"Or nous avons respectivement {.val {config$intermediate_size}} et {.val " +"{config$hidden_size}}" + +#: llama.R:209 +msgid "" +"{.arg config$model_type} must be {.val llama}, got {.val {config$model_type}}" +msgstr "" +"{.arg config$model_type} doit être {.val llama}, or nous avons {.val " +"{config$model_type}}" + +#: llama.R:215 +msgid "Currently only {.val silu} is supported." +msgstr "Actuellement seul {.val silu} est pris en compte." + +#: weights.R:33 +msgid "" +"Error downloading weights from {.val {c(WEIGHTS_NAME(), " +"WEIGHTS_INDEX_NAME())}}" +msgstr "" +"Problème de téléchargement des points depuis {.val {c(WEIGHTS_NAME(), " +"WEIGHTS_INDEX_NAME())}}" + +#: weights.R:34 +msgid "" +"Traceback below shows the error when trying to download {.val " +"{WEIGHTS_NAME()}}" +msgstr "" +"Le log suivant détaille l'erreur lors de la tentative de téléchargement de {." +"val {WEIGHTS_NAME()}}" + +#: weights.R:73 +msgid "No safetensors files found." +msgstr "Aucun fichier safetensors trouvé." diff --git a/po/R-minhub.pot b/po/R-minhub.pot new file mode 100644 index 0000000..b100e47 --- /dev/null +++ b/po/R-minhub.pot @@ -0,0 +1,109 @@ +msgid "" +msgstr "" +"Project-Id-Version: minhub 0.0.0.9000\n" +"POT-Creation-Date: 2024-11-28 11:28+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gpt2.R:201 +msgid "" +"{.arg config$model_type} must be {.val gpt2}, got {.val {config$model_type}}" +msgstr "" + +#: gpt2.R:205 +msgid "{.arg config$layer_norm_eps} must be {.val 1e-5}." +msgstr "" + +#: gpt2.R:209 +msgid "{.arg {names(pdrop)}} must be all equal, but got {pdrop}" +msgstr "" + +#: gpt2.R:214 +msgid "" +"{.arg initializer_range} must be {.val 0.02}, got {config$initializer_range}" +msgstr "" + +#: gptbigcode.R:139 +msgid "{.arg config$model_type} must be {.val gpt_bigcode}." +msgstr "" + +#: gptbigcode.R:140 +msgid "Got {.val {config$model_type}}" +msgstr "" + +#: gptbigcode.R:144 +msgid "Must use {.arg config$multi_query} but got {.val FALSE}" +msgstr "" + +#: gptbigcode.R:149 +msgid "All dropout must be equal." +msgstr "" + +#: gptbigcode.R:150 +msgid "Got {.val {names(dropouts)}} respectively {.val {dropouts}}" +msgstr "" + +#: gptbigcode.R:158 gptneox.R:206 +msgid "" +"{.arg config$layer_norm_eps} must be 1e-5, got {.val {config$layer_norm_eps}}" +msgstr "" + +#: gptneox.R:182 +msgid "" +"{.arg config$model_type} must be {.val gpt_neox}, got {.val " +"{config$model_type}}" +msgstr "" + +#: gptneox.R:188 +msgid "Non parallel residual is not supported." +msgstr "" + +#: gptneox.R:189 +msgid "{.arg config$use_parallel_residual} is {.val FALSE}" +msgstr "" + +#: gptneox.R:194 llama.R:214 +msgid "Unsupported {.arg config$hidden_act}: {.val {config$hidden_act}}" +msgstr "" + +#: gptneox.R:195 +msgid "Currently only {.val gelu} is supported." +msgstr "" + +#: gptneox.R:200 +msgid "{.arg config$intermediate_size} must be 4*{.arg config$hidden_size}" +msgstr "" + +#: gptneox.R:201 +msgid "Got {.val {config$intermediate_size}} and {.val {config$hidden_size}}" +msgstr "" + +#: llama.R:209 +msgid "" +"{.arg config$model_type} must be {.val llama}, got {.val {config$model_type}}" +msgstr "" + +#: llama.R:215 +msgid "Currently only {.val silu} is supported." +msgstr "" + +#: weights.R:33 +msgid "" +"Error downloading weights from {.val {c(WEIGHTS_NAME(), " +"WEIGHTS_INDEX_NAME())}}" +msgstr "" + +#: weights.R:34 +msgid "" +"Traceback below shows the error when trying to download {.val " +"{WEIGHTS_NAME()}}" +msgstr "" + +#: weights.R:73 +msgid "No safetensors files found." +msgstr "" diff --git a/tests/testthat/test-gpt2.R b/tests/testthat/test-gpt2.R index b2423b9..73e44b3 100644 --- a/tests/testthat/test-gpt2.R +++ b/tests/testthat/test-gpt2.R @@ -76,3 +76,13 @@ test_that("can execute gpt2 after moving to different device", { }) +test_that("wrong identifier raise an error", { + identifier <- "Qwen/Qwen2.5-Coder-0.5B" + revision <- "8123ea2" + expect_error( + model <- gpt2_from_pretrained(identifier, revision), + regexp = " must be \"gpt2\", got \"qwen2\"") + +}) + + diff --git a/tests/testthat/test-message-translation.R b/tests/testthat/test-message-translation.R new file mode 100644 index 0000000..b89a998 --- /dev/null +++ b/tests/testthat/test-message-translation.R @@ -0,0 +1,31 @@ +identifier <- "Qwen/Qwen2.5-Coder-0.5B" +revision <- "8123ea2" + +test_that("gpt2 R-level error messages are correctly translated in FR", { + # skip on ubuntu cuda as image as no FR lang installed + skip_if(torch::cuda_is_available() && grepl("linux-gnu", R.version$os)) + # skip on MAC M1 + skip_if(torch::backends_mps_is_available() && R.version$arch == "aarch64") + withr::with_language(lang = "fr", + expect_error( + model <- gpt2_from_pretrained(identifier, revision), + regexp = " doit être \"gpt2\", or elle est à \"qwen2\"", + fixed = TRUE + ) + ) +}) + +test_that("gptbigcode R-level error messages are correctly translated in FR", { + # skip on ubuntu cuda as image as no FR lang installed + skip_if(torch::cuda_is_available() && grepl("linux-gnu", R.version$os)) + # skip on MAC M1 + skip_if(torch::backends_mps_is_available() && R.version$arch == "aarch64") + + withr::with_language(lang = "fr", + expect_error( + model <- gptbigcode_from_pretrained(identifier, revision), + regexp = " doit être \"gpt_bigcode\".\nOr elle est à \"qwen2\"", + fixed = TRUE + ) + ) +})