Examples

August 12, 2026 ยท View on GitHub

Home | Installation | Bioconductor | Implementation | Examples | Benchmarks | API | Reproducibility | References

Iris KNN-First Workflow

library(fastEmbedR)

x <- scale(as.matrix(iris[, 1:4]))
labels <- iris$Species

y_tsne <- fastEmbedR::opentsne(
  x, perplexity = 10, backend = "cpu", n.cores = 4, seed = 1
)$layout
y_umap <- fastEmbedR::umap(
  x, n_neighbors = 15, backend = "cpu", n.cores = 4,
  graph_mode = "fuzzy", seed = 1
)$layout

plot(y_tsne, pch = 21, bg = labels)
plot(y_umap, pch = 21, bg = labels)

Iris One-Call openTSNE

fit <- fastEmbedR::opentsne(
  x,
  perplexity = 30,
  backend = "cpu",
  n.cores = 4,
  seed = 1
)

plot(fit)
fit$metrics

Use backend = "metal" on Apple Silicon or backend = "cuda" on a CUDA build. Explicit GPU requests fail clearly if the backend is unavailable. For matrix input, CPU uses native HNSW and Metal uses native exact or recall-tuned IVF-Flat. CUDA uses package-native cuVS GPU-resident KNN. The internal non-self KNN width is ceiling(perplexity). Use opentsne_knn() with a plain precomputed host KNN list when benchmarking an alternative search.

Iris One-Call UMAP

Standard UMAP fuzzy weighting is the default:

fit <- fastEmbedR::umap(
  x,
  n_neighbors = 30,
  backend = "cpu",
  n.cores = 4,
  seed = 1
)

plot(fit)
fit$metrics

For matrix input, umap() uses the same fixed KNN policy as opentsne(). Use umap_knn() when you want to reuse or benchmark a separately computed KNN object.

MNIST 70k Benchmark Example

The example below uses the full 70,000 MNIST observations as flattened 28x28 images. CPU paths are requested with 4 threads. The result figure places t-SNE/openTSNE methods on the first row and UMAP methods on the second row.

library(fastEmbedR)
library(Rtsne)
library(uwot)

Sys.setenv(
  OMP_NUM_THREADS = "4",
  OPENBLAS_NUM_THREADS = "4",
  MKL_NUM_THREADS = "4",
  RCPP_PARALLEL_NUM_THREADS = "4"
)

load("/path/to/MNIST.RData")          # classic object named dataset
x_ref <- as.matrix(dataset$data)
labels <- as.factor(dataset$labels)

x_fast <- x_ref
out_dir <- "mnist70k_fastembedr_example"
dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)

k <- 30
perplexity <- 15
seed <- 4

time_it <- function(expr) {
  t0 <- proc.time()[["elapsed"]]
  value <- force(expr)
  list(value = value, sec = proc.time()[["elapsed"]] - t0)
}

layout_of <- function(obj) {
  y <- obj$value
  if (is.list(y) && !is.null(y$layout)) y <- y$layout
  if (is.list(y) && !is.null(y$Y)) y <- y$Y
  as.matrix(y)
}

run_method <- function(method, backend, expr) {
  set.seed(seed)
  ans <- tryCatch(time_it(expr), error = function(e) e)
  if (inherits(ans, "error")) {
    data.frame(method = method, backend = backend, total_sec = NA_real_,
               status = "failed", error_message = conditionMessage(ans))
  } else {
    layouts[[method]] <<- layout_of(ans)
    data.frame(method = method, backend = backend, total_sec = ans$sec,
               status = "success", error_message = NA_character_)
  }
}

layouts <- list()
results <- list()

results[[length(results) + 1L]] <- run_method(
  "fastEmbedR openTSNE CPU", "cpu",
  fastEmbedR::opentsne(x_fast, perplexity = perplexity, backend = "cpu",
                       n.cores = 4, seed = seed)
)

results[[length(results) + 1L]] <- run_method(
  "fastEmbedR openTSNE CUDA", "cuda",
  fastEmbedR::opentsne(x_fast, perplexity = perplexity, backend = "cuda",
                       n.cores = 4, seed = seed)
)

results[[length(results) + 1L]] <- run_method(
  "Rtsne full", "cpu",
  Rtsne::Rtsne(x_ref, perplexity = perplexity, check_duplicates = FALSE,
               pca = TRUE, num_threads = 4)
)

results[[length(results) + 1L]] <- run_method(
  "fastEmbedR UMAP CPU fuzzy", "cpu",
  fastEmbedR::umap(x_fast, n_neighbors = k, backend = "cpu",
                   graph_mode = "fuzzy", n.cores = 4, seed = seed)
)

results[[length(results) + 1L]] <- run_method(
  "fastEmbedR UMAP CUDA fuzzy", "cuda",
  fastEmbedR::umap(x_fast, n_neighbors = k, backend = "cuda",
                   graph_mode = "fuzzy", n.cores = 4, seed = seed)
)

results[[length(results) + 1L]] <- run_method(
  "uwot UMAP fast_sgd full", "cpu",
  uwot::umap(x_ref, n_neighbors = k, fast_sgd = TRUE,
             n_threads = 4, n_sgd_threads = 4, verbose = FALSE)
)

timing <- do.call(rbind, results)
write.csv(timing, file.path(out_dir, "mnist70k_timing.csv"), row.names = FALSE)

print(timing)

ok <- timing$status == "success"
png(file.path(out_dir, "mnist70k_time_barplot.png"), width = 1200, height = 700, res = 140)
op <- par(mar = c(8, 4, 2, 1))
barplot(timing$total_sec[ok], names.arg = timing$method[ok],
        las = 2, ylab = "Total time (seconds)", col = "#4C78A8")
par(op)
dev.off()

cols <- as.integer(labels)
png(file.path(out_dir, "mnist70k_embeddings.png"), width = 2700, height = 1560, res = 150)
par(mfrow = c(2, 3), mar = c(1, 1, 3, 1))
for (nm in names(layouts)) {
  y <- layouts[[nm]]
  plot(y[, 1], y[, 2], pch = 16, cex = 0.22, col = cols,
       axes = FALSE, xlab = "", ylab = "", main = nm)
  box(col = "grey70")
}
dev.off()

The example compares:

  • fastEmbedR::opentsne() on CPU, Metal, and/or CUDA;
  • Rtsne::Rtsne() as the full Rtsne baseline with its own internal KNN;
  • fastEmbedR::umap(..., graph_mode = "fuzzy") on CPU, Metal, and/or CUDA;
  • uwot::umap(..., fast_sgd = TRUE) as the full uwot baseline with its own internal KNN.

This run used:

  • Machine: icgeb-bioinformatics-unit
  • System: Linux 6.8.0-124-generic, x86_64
  • CPU: 13th Gen Intel(R) Core(TM) i7-13700
  • GPU: NVIDIA GeForce RTX 5060 Ti, driver 595.71.05, 16311 MiB
  • RAM: 31.02 GB
  • R: 4.5.3
  • fastEmbedR: 0.1.0
  • uwot: 0.2.4
  • Rtsne: 0.17
  • Requested benchmark threads: 4

The benchmark intentionally does not show graph_mode = "binary".

MNIST 70k Results

The following presentation view summarizes the publication benchmark rather than the single run shown below. It reports median end-to-end public-function runtime and interquartile range over three seeds on linear axes with a true zero baseline. Bar-end labels state how many times faster the fastest method in each panel was (1x marks the fastest method). CPU and CUDA measurements used an Intel Xeon Gold 6442Y and NVIDIA L40S; Metal measurements used an Apple M3 and are identified separately rather than treated as matched-machine comparisons.

MNIST 70k linear-scale runtime comparison with speed ratios

Presentation assets:

The single-run example output remains below so that its table, machine description, and embedding panels stay tied to the executable R example.

MNIST 70k computational time

methodbackendtotal sectrustlabel KNN acc
fastEmbedR openTSNE CPUCPU46.6400.3300.969
fastEmbedR openTSNE CUDACUDA5.2860.3320.965
Rtsne fullCPU94.1310.3230.972
fastEmbedR UMAP CPU fuzzyCPU26.5070.2810.971
fastEmbedR UMAP CUDA fuzzyCUDA4.3970.2780.970
uwot UMAP fast_sgd fullCPU47.8660.2790.970

MNIST 70k embeddings

Source files: