Cmdstanr: read_cmdstan_csv efficiency issue for large number of parameters

Created on 18 Sep 2020  路  11Comments  路  Source: stan-dev/cmdstanr

Describe the bug
CSV read speed drastically degrades when the number of params is large(>10^5) .

To Reproduce
@billgillespie and I ran into a model with 10^6 params and read_cmdstan_csv becomes stalled . I was able to reproduce it with the following dummy model

parameters {
  real k;
}

transformed parameters {
  real x[100000] = rep_array(k, 100000);
}

model {
  k ~ normal(0, 1);
}

The output file contains only one sample:

./dummy sample algorithm=fixed_param num_warmup=0 num_samples=1 output file=dummy.csv

rstan::read_stan_csv vs cmdstanr::read_cmdstan_csv:

> system.time(fit <- cmdstanr::read_cmdstan_csv("dummy.csv"))
   user  system elapsed 
 55.873  26.429  67.056 
> system.time(fit <- rstan::read_stan_csv("dummy.csv"))
   user  system elapsed 
  5.449   0.341   5.847 

Expected behavior
More realistic reading speed in this scenario.

Operating system
macos mojave

> sessionInfo()
R version 3.6.2 (2019-12-12)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Mojave 10.14.6

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] compiler_3.6.2 tools_3.6.2    tcltk_3.6.2   

CmdStanR version number

> library("cmdstanr")
This is cmdstanr version 0.1.3
- Online documentation and vignettes at mc-stan.org/cmdstanr
- Use set_cmdstan_path() to set the path to CmdStan
- Use install_cmdstan() to install CmdStan
bug

Most helpful comment

fwiw, it was taking a super long time on a recent model for fit$summary() that I just stopped it. Running the above from Rok on my model yields

> a <- Sys.time()
> t1 <- rstan::read_stan_csv(fit_vi$output_files())
Warning messages:
1: In parse_stancsv_comments(comments) :
  line with "Elapsed Time" not found
2: In validityMethod(object) :
  The following variables have undefined values:  log_p__. Many subsequent functions will not work correctly.
> print(Sys.time()-a)
Time difference of 3.083478 mins
> 
> a <- Sys.time()
> t2 <- list()
> for (f in fit_vi$output_files()) {
+   t2[[f]] <- fread(cmd= paste0("grep -v '^#' ", f))
+ }
> print(Sys.time()-a)
Time difference of 20.21756 secs

9x faster! On the base ec2 (2 cores, 8 gb mem) linux ubuntu 20.04

All 11 comments

You're not seeing a message that says "Fast CSV reading with vroom::vroom() failed." are you?

If not, then reading the CSVs already employs the current fastest csv-reading method, vroom. Someday cmdstan may move to a proper binary output format and be ridiculously faster (see here for discussions on this), but for now we're stuck with the current speeds.

Ah, slight correction: I guess data.table::fread() is slightly faster than vroom::vroom() (see here), and while it's possible that cmdstanr might move to the former for it's lighter dependency requirements, that probably won't yield noticeable speed improvements.

Thanks for the report @yizhang-yiz and sorry for the issues. Will take a look this weekend.

You're not seeing a message that says "Fast CSV reading with vroom::vroom() failed." are you?

No.

I guess data.table::fread() is slightly faster than vroom::vroom()

@billgillespie just tested it and fread is much faster.

@billgillespie just tested it and fread is much faster.

Cool! Did you guys do this test in a fork of cmdstanr? If so, feel free to do a pull request!

FYI, from vroom itself:

https://vroom.r-lib.org/articles/benchmarks.html#all-numeric-data

All numeric data is really a worst case scenario for vroom. The index takes about as much memory as the parsed data. Also because parsing doubles can be done quickly in parallel and text representations of doubles are only ~25 characters at most there isn鈥檛 a great deal of savings for delayed parsing.

For these reasons (and because the data.table implementation is very fast) vroom is a bit slower than fread for pure numeric data.

image

Sorry for the radio silence on this issue. After debugging this issue for some time I took another look at fread and some preliminary tests show that we can just replace vroom with data.table and its fread function. Either data.table was updated recently to play nicer with Stan CSV or I was sloppy in my initial testing of fread.

this will get sorted by replacing vroom with fread which will also close https://github.com/stan-dev/cmdstanr/issues/262 and https://github.com/stan-dev/cmdstanr/issues/198

Can someone on a mac try the following:

library(cmdstanr)
library(data.table)

model_code <- "
parameters {
  real k;
}

transformed parameters {
  real x[100000] = rep_array(k, 100000);
}

model {
  k ~ normal(0, 1);
}
"
model_file <- write_stan_file(model_code)
mod <- cmdstan_model(model_file)
fit <- mod$sample(iter_sampling = 1, iter_warmup = 200, validate_csv = FALSE, chains = 4)

a <- Sys.time()
t1 <- rstan::read_stan_csv(fit$output_files())
print(Sys.time()-a)

a <- Sys.time()
t2 <- list()
for (f in fit$output_files()) {
  t2[[f]] <- fread(cmd= paste0("grep -v '^#' ", f))
}
print(Sys.time()-a)

The grep thing is ugly but necessary as fread does not support comment.char. There is an equivalent Windows approach to this. On Linux this is really fast (the bottom approach).

Running MCMC with 4 sequential chains...

Chain 1 Iteration:   1 / 201 [  0%]  (Warmup) 
Chain 1 Iteration: 100 / 201 [ 49%]  (Warmup) 
Chain 1 Iteration: 200 / 201 [ 99%]  (Warmup) 
Chain 1 Iteration: 201 / 201 [100%]  (Sampling) 
Chain 1 finished in 0.2 seconds.
Chain 2 Iteration:   1 / 201 [  0%]  (Warmup) 
Chain 2 Iteration: 100 / 201 [ 49%]  (Warmup) 
Chain 2 Iteration: 200 / 201 [ 99%]  (Warmup) 
Chain 2 Iteration: 201 / 201 [100%]  (Sampling) 
Chain 2 finished in 0.2 seconds.
Chain 3 Iteration:   1 / 201 [  0%]  (Warmup) 
Chain 3 Iteration: 100 / 201 [ 49%]  (Warmup) 
Chain 3 Iteration: 200 / 201 [ 99%]  (Warmup) 
Chain 3 Iteration: 201 / 201 [100%]  (Sampling) 
Chain 3 finished in 0.2 seconds.
Chain 4 Iteration:   1 / 201 [  0%]  (Warmup) 
Chain 4 Iteration: 100 / 201 [ 49%]  (Warmup) 
Chain 4 Iteration: 200 / 201 [ 99%]  (Warmup) 
Chain 4 Iteration: 201 / 201 [100%]  (Sampling) 
Chain 4 finished in 0.2 seconds.

All 4 chains finished successfully.
Mean chain execution time: 0.2 seconds.
Total execution time: 0.9 seconds.
> Time difference of 8.561616 secs
> + + > Time difference of 1.311881 secs
> 

Cool, thanks.

At least for unix systems then this looks promising. For windows we can use findstr.
Hopefully that will work.

No comment.char in fread is a longstanding issue (https://github.com/Rdatatable/data.table/issues/856#issue-comment-box) and there does not seem to be much traction there, so this grep/findstr thing will have to do.

fwiw, it was taking a super long time on a recent model for fit$summary() that I just stopped it. Running the above from Rok on my model yields

> a <- Sys.time()
> t1 <- rstan::read_stan_csv(fit_vi$output_files())
Warning messages:
1: In parse_stancsv_comments(comments) :
  line with "Elapsed Time" not found
2: In validityMethod(object) :
  The following variables have undefined values:  log_p__. Many subsequent functions will not work correctly.
> print(Sys.time()-a)
Time difference of 3.083478 mins
> 
> a <- Sys.time()
> t2 <- list()
> for (f in fit_vi$output_files()) {
+   t2[[f]] <- fread(cmd= paste0("grep -v '^#' ", f))
+ }
> print(Sys.time()-a)
Time difference of 20.21756 secs

9x faster! On the base ec2 (2 cores, 8 gb mem) linux ubuntu 20.04

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rok-cesnovar picture rok-cesnovar  路  12Comments

nerutenbeck picture nerutenbeck  路  4Comments

andrjohns picture andrjohns  路  6Comments

beyondpie picture beyondpie  路  8Comments

SteveBronder picture SteveBronder  路  7Comments