Neuracore Robot Learning Library

repository·main·Indexed 42 days ago

https://github.com/neuracoreai/neuracore

A robot learning library for streamlining the robot learning lifecycle, including data collection, visualization, cloud-based model training, and real-time policy inference. Version 17.0.1 provides a Python API and CLI for managing datasets, launching training runs with automatic batch-size tuning, and deploying models locally or to server endpoints. It includes support for ROS2 integration and humanoid scenarios via BiGym.

Tokens
48.9K
Snippets
150
Records
194
Agent score
54%

What's inside Neuracore

  1. Overview of the Neuracore Dataset Importer

    main

    The neuracore.importer module is designed to facilitate the transition of robot datasets into the Neuracore ecosystem.

    Key Capabilities:

    • Format Support: Import datasets from MCAP, RLDS, and LeRobot formats.
    • Automatic Detection: Automatically identifies dataset types based on the provided file structure.
    • Data Mapping: Provides a flexible system to map source data (such as images, joint states, poses, and language) to Neuracore's standardized data types.
    • Cloud Integration: Uploads datasets to Neuracore while maintaining correct associations with robot models.
    • Validation: Validates incoming data against your provided configuration and the constraints of the associated robot model.
  2. What is backpressure in the Data Daemon?

    main

    Backpressure is a protective mechanism used by the Data Daemon to prevent unbounded disk or memory usage when video data is produced faster than it can be encoded and uploaded.

    When using log_rgb or log_depth, raw frames are written to an on-disk staging area called the spool (.rgb_spool/ under your recordings root). The daemon reads from this spool to transcode chunks to MP4 via ffmpeg.

    If the daemon cannot drain the spool fast enough, the backlog grows toward a limit (default 2 GiB). Once full, log_rgb/log_depth calls will block for up to 1 second. If the daemon still hasn't caught up, the system raises a LoggingStalledError to prevent silent data loss or disk exhaustion.

  3. Convert gripper amounts to visual joint positions

    main

    To populate visual joints (used for URDF visualization) from gripper open/close data, set visual_joint_input_type: GRIPPER in the VISUAL_JOINT_POSITIONS format.

    Requirements:

    1. A valid URDF/MJCF model.
    2. Joint names in the mapping that exist in the robot model.
    3. Joint limits defined in the robot model (used for automatic unnormalization).
    4. Gripper open amounts data (typically from PARALLEL_GRIPPER_OPEN_AMOUNTS).
    # Converting gripper open amounts (0-1) to individual gripper finger positions
    VISUAL_JOINT_POSITIONS:
      source: observation.state
      format:
        visual_joint_input_type: GRIPPER
      mapping:
        - name: finger_joint1
          index: 6
        - name: finger_joint2
          index: 6
  4. Use Forward Kinematics (FK) to convert joint positions to poses

    main

    If your dataset contains joint positions but you need end-effector poses, set ee_pose_input_type: JOINT_POSITIONS in the END_EFFECTOR_POSES format. This requires a valid URDF/MJCF and an end-effector frame name defined in the mapping.

    Requirements:

    1. A valid robot model (URDF/MJCF).
    2. An end-effector frame name (provided via mapping.name).
    # Joint positions to convert from
    JOINT_POSITIONS:
      source: observation.state
      format:
        angle_units: RADIANS
      mapping:
        - name: joint_1
        - name: joint_2
        - name: joint_3
        - name: joint_4
        - name: joint_5
        - name: joint_6
        - name: joint_7
    
    # End-effector pose calculated using Forward Kinematics
    END_EFFECTOR_POSES:
      source: observation.state
      format:
        ee_pose_input_type: JOINT_POSITIONS
      mapping:
        - name: ee_frame_name
  5. Map data types in the Importer configuration

    main

    Each data type in data_import_config uses a source (path/key in source data), a format (transformation rules), and a mapping (list of items to save).

    Mapping Logic by Dataset Type:

    • RLDS: source is a key in the step/episode dictionary; source_name is the key within that dictionary.
    • LeRobot: source_name refers to the column name in the Arrow table.
    • MCAP: Paths use /topic[.nested.field.path] syntax. A source_name starting with / is an absolute path to a topic.

    Common Mapping Options:

    • name: The name to be saved in Neuracore (required).
    • source_name: The key/column/topic in the source.
    • index: Index in source array.
    • index_range: { "start": int, "end": int } for slicing.
    • inverted: Boolean to flip the sign.
    • offset: Value to add/subtract from the data.
  6. Align new robots to trained models via padding

    main

    When deploying a model to a new robot with a different hardware configuration (e.g., different number of joints or cameras), you must use an EmbodimentDescription to semantically align the new hardware to the model's learned structure.

    If the new robot has fewer components than the training data, you must leave the missing indices empty (effectively zero-padding) so that the components that do exist match their trained indices.

    Example: Aligning an SO-100 (5 joints) to a model trained on Franka (7 joints) and UR5 (6 joints)

    RobotJoint ID 0Joint ID 1Joint ID 2Joint ID 3Joint ID 4Joint ID 5Joint ID 6
    frankaf_basef_joint_1f_joint_2f_joint_3f_joint_4f_joint_5f_wrist
    ur5ur5_baseur5_1ur5_2ur5_3ur5_40ur5_wrist
    so100joint_1joint_2joint_3joint_400wrist

    Important: RGB inputs must follow the same positional convention. If the model was trained with [wrist_camera, overhead_camera], an overhead camera must be placed at index 1, even if no wrist camera is present.

    so100_embodiment_description: EmbodimentDescription = {
        JOINT_POSITIONS: {
            0: "joint_1",
            1: "joint_2",
            2: "joint_3",
            3: "joint_4",
            6: "wrist",
        },
        RGB_IMAGES: {
            1: "so100_overhead_camera",
        },
    }
  7. Configure video encoding (Codec)

    main

    The daemon supports two video codecs for RGB cameras. This setting is global and applies to every camera. The daemon re-reads the codec at the start of each recording, so changes take effect for the next recording without a daemon restart.

    • h264_medium (Default): Lossy-only. Uploads a single full-resolution video (lossy.mp4). This is irreversible; if you record in this mode, you cannot later generate a lossless archive for that recording.
    • h264_lossless: Uploads both a lossless archive (lossless.mp4) for training and a small lossy preview (lossy.mp4). This increases disk use, upload size, and CPU usage.

    Note: Depth cameras always maintain lossless storage regardless of this setting.

    import neuracore as nc
    
    # Set to lossy-only (default)
    nc.set_video_encoding_options(codec=nc.Codec.H264_MEDIUM)
    
    # Set to lossless (uploads both lossy and lossless files)
    nc.set_video_encoding_options(codec=nc.Codec.H264_LOSSLESS)
  8. Understand the Rust Data Daemon workspace layout

    main

    The Rust data daemon is organized as a Cargo workspace located in the rust/ directory. It consists of three primary crates:

    • data-daemon: The main daemon binary. It handles the CLI, lifecycle management, SQLite state, IPC listening, per-trace pipelines, and encoding.
    • data_daemon_shared: A shared library containing IPC envelope types, service-name constants, the daemon configuration model, and filesystem-path resolution logic. This is linked by both the daemon and the producer.
    • data_daemon_bridge: A PyO3 cdylib that acts as the producer-side IPC client. It is exposed to Python as neuracore.data_daemon._data_bridge.
    | Crate | Path | What it is |
    |---|---|---|
    | `data-daemon` | [rust/data_daemon/](../rust/data_daemon/) | The daemon binary — CLI, lifecycle, SQLite state, IPC listener, per-trace pipeline, encoding. |
    | `data_daemon_shared` | [rust/data_daemon_shared/](../rust/data_daemon_shared/) | Shared library — IPC envelope types and service-name constants, plus the daemon configuration model and filesystem-path resolution the two processes must compute identically. Linked by both the daemon and the producer crate. |
    | `data_daemon_bridge` | [rust/data_daemon_bridge/](../rust/data_daemon_bridge/) | PyO3 `cdylib` — producer-side IPC client exposed to Python as `neuracore.data_daemon._data_bridge`. |
  9. Use Inverse Kinematics (IK) to convert poses to joint positions

    main

    If your dataset contains end-effector poses but you need joint positions, set joint_position_input_type: END_EFFECTOR in the JOINT_POSITIONS format. This requires a valid URDF/MJCF and an end-effector frame name defined in the mapping.

    Requirements:

    1. A valid robot model (URDF/MJCF).
    2. An end-effector frame name (provided via mapping.name).
    3. Optional ik_init_config (a list of floats) to provide an initial joint configuration for the IK solver.
    # End-effector pose to convert from
    END_EFFECTOR_POSES:
        source: observation.state
        mapping:
          - name: ee_name
            index_range:
              start: 0
              end: 7
    
    # Joint position calculated using Inverse Kinematics
    JOINT_POSITIONS:
      source: observation.state
      format:
        joint_position_input_type: END_EFFECTOR
        ik_init_config: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
      mapping:
        - name: ee_frame_name
          source_name: ee_name
  10. How model construction and dimensionality work in Neuracore

    main

    In Neuracore, model shape is spec-driven, not robot-driven. Instead of hard-coding tensor dimensions based on a specific robot, you define a declarative specification that maps data points (like joints or camera feeds) into a shared index space.

    When launching a training job, you must provide an Input Cross-Embodiment Description and an Output Cross-Embodiment Description. These specifications determine the fixed input and output dimensionality for the model. This approach allows a single model to train across heterogeneous robots by ensuring that every dimension in the tensor has a consistent semantic meaning (e.g., index 2 always represents 'elbow bend' regardless of the robot's specific joint names).

    # The model shape is determined by these two specs:
    input_cross_embodiment_description: CrossEmbodimentDescription = { ... }
    output_cross_embodiment_description: CrossEmbodimentDescription = { ... }
  11. Configure Relative Joint Target Positions

    main

    You can specify joint target positions as delta values relative to the current robot state. The importer will add these deltas to the current state to form the final target.

    Modes:

    • Joint Space: If action_space: JOINT, deltas are applied to current joint positions.
    • End-Effector Space: If action_space: END_EFFECTOR, deltas are applied to the current end-effector pose. This requires importing end-effector poses.

    Configuration:

    • action_type: RELATIVE must be set.
    • action_space must be set to either JOINT or END_EFFECTOR.
    # Example 1: Relative action in pose space
    JOINT_TARGET_POSITIONS:
      source: action
      format:
        action_type: RELATIVE
        action_space: END_EFFECTOR
        pose_type: POSITION_ORIENTATION
        orientation:
          type: EULER
          euler_order: XYZ
          angle_units: RADIANS
      mapping:
        - name: ee_frame_name
          index_range:
            start: 7
            end: 13
    
    # Example 2: Relative action in joint space
    JOINT_TARGET_POSITIONS:
      source: action
      format:
        action_type: RELATIVE
        action_space: JOINT
      mapping:
        - name: joint_1
        - name: joint_2
        - name: joint_3
        - name: joint_4
        - name: joint_5
        - name: joint_6
        - name: joint_7
  12. How Automatic Batch-Size Tuning works

    main

    Neuracore uses a linear memory model to predict the maximum batch size. The core idea is that peak GPU memory follows the formula:

    peak(b) ≈ intercept + slope · b

    Where:

    • intercept: Fixed memory costs (model weights, optimizer state, CUDA context).
    • slope: Extra memory required per additional sample (activations).

    The Algorithm

    1. Probe min_batch_size: If this OOMs, the model is too large for the GPU.
    2. Probe high anchor: Usually min_batch_size * 8. If this OOMs, the algorithm shrinks toward the minimum.
    3. Place a third anchor: A larger batch size calculated to find a reliable slope.
    4. Fit the slope: The algorithm calculates the slope specifically between the 2nd and 3rd probes. It avoids using the slope from the first two tiny probes because they are dominated by the intercept (fixed costs) and provide an unreliable, underestimated slope.
    5. Predict and Confirm: It predicts the b_max that hits the max_gpu_utilization budget and runs a probe to confirm. If it OOMs, it shrinks by _DOWNSCALE_FACTOR (0.9) until it fits.
    6. Apply Safety Margin: The final chosen batch is floor(largest_fitting_batch * safety_factor).