CmdStanR

repository·master·Indexed 41 days ago

https://github.com/stan-dev/cmdstanr

A lightweight R interface to the CmdStan ecosystem that allows R users to run Stan models by calling compiled executables rather than interfacing directly with C++ code. It features a decoupled architecture, a clean interface for running Stan's algorithms, and a robust compilation state record system to manage model binaries and provenance.

Tokens
19K
Snippets
35
Records
80
Agent score
51%

What's inside CmdStanR

  1. Overview of CmdStanR

    master

    CmdStanR is a lightweight R interface to Stan. Unlike other interfaces, it does not interface directly with C++ code; instead, it calls compiled Stan executables.

    Key characteristics include:

    • Clean Interface: Designed to keep pace with Stan releases.
    • Modularity: CmdStanR handles running Stan's algorithms, allowing downstream R modules to handle the subsequent analysis.
    • Decoupled Architecture: By calling compiled executables rather than linking to C++ code, it maintains a clear separation between the R interface and the Stan engine.
  2. How the new `cmdstan_model()` API works

    master

    The updated API (Stage 4) replaces deferred compilation and the $compile() method with a unified constructor-based decision engine.

    Key Concepts:

    • The Decision Engine: When you call cmdstan_model(), the engine assesses whether the existing executable is valid based on the provided cpp_options and the recorded state. If there is a mismatch (e.g., you change a thread setting), it triggers a rebuild.
    • Standalone Family: For models where the source is available, use the standalone functions stan_variables(file, include_paths = ...) and check_syntax_stan_file(file, include_paths = ...) to interact with the source directly.
    • Executable-only Adoption: For models built at install-time (where the source is no longer in a predictable location), cmdstan_model() operates in an 'executable-only' mode. It uses the recorded metadata to manage the binary without needing to re-resolve include paths at runtime.
    # New API pattern: The constructor handles the decision logic
    mod <- cmdstan_model(file, cpp_options = list(stan_threads = TRUE))
  3. Understand the compilation state record

    master

    The compilation state record describes how a specific executable was created. It is a factual account of the build process and is not a general configuration store. It consists of several key fields:

    • request: The build configuration. This includes stanc_options (both user-supplied and cmdstanr-injected), cpp_options (the actual flags used by the build), and effective include_paths.
    • reported_features: What the binary actually reports as enabled (e.g., threading, OpenCL, Stan version). This is tri-state and best-effort (known enabled, known disabled, or unknown). Absence must never be read as disabled.
    • dependencies: The source files consumed during the build.
    • artifact: A hash of the executable used to verify that a record and an executable match.
    • builder: The CmdStan installation used.
    • format_version: The version of the record format.

    Note that request and reported_features are never merged. For example, $cpp_options() reports only what the caller supplied, while stan_build_info() reports what the binary itself claims.

  4. Understand the difference between CmdStanR and RStan

    master

    CmdStanR and RStan use fundamentally different architectures to interface with Stan's C++ code:

    • CmdStanR: Uses an external process model. It runs CmdStan as a separate process and reads the resulting output files (typically CSVs). This isolates inference from the R session, reducing the risk of R session termination during failures and potentially lowering R memory usage because draws are only loaded into R when requested.
    • RStan: Uses an in-memory interface. It relies on packages like Rcpp to call C++ code directly from within the R process.

    While CmdStanR primarily uses external processes, it does offer optional features like $expose_functions() and additional model methods that use Rcpp to call compiled C++ code directly from R.

  5. Note on reformatting and recompilation

    master

    Calling $format() on a model object will overwrite the source file. Because changing the bytes of the source file changes the model, reformatting forces a recompile the next time a guarded method is called or cmdstan_model() is used.

    # Reformatting the file will change the source bytes,
    # necessitating a recompile for subsequent sampling.
    model$format(overwrite_file = TRUE)
  6. How model rebuilds are triggered in cmdstanr

    master

    A model is rebuilt if any of the following conditions are met:

    1. Explicit Recompile: You set force_recompile = TRUE in the call.
    2. Input Changes: Any field that affects the model's identity has changed. This includes the Stan program source, resolved includes, the user header, and the make/local configuration file.
    3. Artifact Inconsistency: The existing executable does not match the recorded artifact (e.g., it was replaced or is corrupt), the build record is missing/unreadable, or the executable predates the build records.

    Note on include_paths: Changing the spelling or location of include_paths does not trigger a rebuild directly. Instead, cmdstanr re-runs stanc --info to resolve the files. A rebuild is triggered only if the content of the resolved files changes. This prevents unnecessary rebuilds when simply renaming directories that do not change the actual file content being included.

  7. When a model rebuild is triggered

    master

    A rebuild of a Stan model is automatically triggered by cmdstanr under the following conditions:

    1. Manual Trigger: Setting force_recompile = TRUE in the build call.
    2. Configuration Changes: Any field in the build configuration that is marked as compared (as defined in the internal compilation state contract) differs from the current call's computed values.
    3. Source Changes: Any change to the Stan source file content or its basename (renaming the file).
    4. Dependency Changes: Changes to files identified by content hashes (dependencies).
    5. Invalid Artifacts:
      • The executable does not match the recorded artifact (e.g., it was replaced or is corrupt).
      • The build record is missing, unreadable, or uses an unsupported format_version.
      • The executable predates existing build records, leaving nothing to compare against.
    6. Installation Changes: A change in the CmdStan installation path or version.

    Note on include_paths: Changes to include_paths trigger a rebuild only if they alter the resulting file resolution (checked via stanc --info content hashes).

    # Example of forcing a recompile
    # (Conceptual usage based on contract description)
    cmdstanr::cmdstan_model(..., force_recompile = TRUE)
  8. How include shadowing is detected

    master

    To prevent silently running the wrong binary due to changed include paths, CmdStanR detects 'include shadowing'.

    It works by:

    1. Recording the files reported by stanc --info at build time.
    2. Hashing the content of those files.
    3. Comparing those hashes positionally against a fresh resolution from stanc --info using the current include_paths.

    This ensures that if a higher-priority include path is added to your configuration, CmdStanR will detect that the resolved file set has changed and trigger a rebuild.

    {
      "parameters": { "y": { "type": "real", "dimensions": 0 } },
      "included_files": [
        "/abs/path/to/inc/half.stan"
      ]
    }
  9. Understand when CmdStanR validates compilation state

    master

    CmdStanR uses an assessment mechanism to determine if an executable is current. This assessment is separate from the action taken by the caller. The engine itself never compiles or mutates state during assessment; it only answers the question: is this executable current?.

    How the system responds depends on the method called:

    • cmdstan_model() or the constructor: If the assessment finds a mismatch (e.g., changed source, changed configuration, or a corrupt record), it rebuilds the model and prints the reasons for the rebuild.
    • Guarded methods: Any operation that runs or derives state from the binary (e.g., $sample(), $optimize()) will error if the assessment detects that the executable is not current.
    • Accessors: Methods like $exe_file() or $cpp_options() are plain accessors that never error and do not trigger validation.
    # Rebuilds if necessary
    model <- cmdstan_model("model.stan")
    
    # Errors if the executable is out of date
    model$sample(data = my_data)
    
    # Never errors, even if the file is missing or out of date
    model$exe_file()
  10. How threading and feature requests are validated

    master

    CmdStanR uses an asymmetric policy when matching runtime arguments (like threads_per_chain) to the capabilities reported by the compiled binary. This prevents expensive silent failures where a user requests parallelism that the binary cannot provide.

    Runtime Request vs. Feature Capability

    Runtime requestFeature enabledFeature disabled or unknown
    threads > 1proceederror
    threads == 1proceedproceed
    no threads argumentproceedproceed
    • Error Case: If you request more than one thread (threads_per_chain > 1) but the binary was not built with threading enabled (or its status is unknown), CmdStanR will throw an error. This prevents a multi-hour job from running single-threaded without the user knowing.
    • No-Op Case: If you do not provide a threads argument, or if you explicitly request only one thread, the run will proceed regardless of whether the binary supports threading or not.
    • Capability Surplus: If a binary is built with threading enabled but you do not request it, the run proceeds normally using CmdStan's defaults (usually 1 thread, unless STAN_NUM_THREADS is set in your environment).
  11. How runtime feature requests interact with build features

    master

    When a runtime argument (like threads) requires a specific build feature, the following logic applies:

    Runtime requestFeature enabledFeature disabled or unknown
    threads > 1proceed
    threads == 1proceed
    no threads argumentproceed
    no threads argumentproceed

    Key Rules:

    • If a feature is known enabled, the request proceeds.
    • If a feature is known disabled, the operation results in an error.
    • If a feature is unknown, the operation results in an error. Never assume an unknown feature is disabled.

    Asymmetric Policy: A binary built with threading enabled is not an error if run without a threads argument. The variable is scoped to the child process via the env argument of processx::process$new().

  12. Understand how include paths are resolved for Stan models

    master

    When compiling a Stan model that contains #include statements, cmdstanr provides a default behavior to ensure models with relative includes can build without explicit configuration. If no include_paths are supplied, cmdstanr defaults the include paths to the directory containing the model file itself.

    This behavior is critical for multi-file models and packages (like those built with instantiate) that rely on relative paths for utility files (e.g., #include utils/silly.stan).

    Note: This is a cmdstanr default; the underlying stanc compiler does not perform this automatic directory resolution.

    # Example: A model with a relative include
    # model.stan
    #include "utils/silly.stan"
    
    # cmdstanr will automatically add the directory of model.stan to the include paths
    # so that the include succeeds.