Skip to content
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3f48d73
Implement --auto-parallelism
pefontana Jan 23, 2026
653d0eb
parallelism_data.json
pefontana Jan 23, 2026
01a6714
Merge branch 'main' into hardcode-parallelism-data
pefontana Jan 23, 2026
b541cc5
simplify code
pefontana Jan 23, 2026
4a6e1d2
Merge remote-tracking branch 'origin/hardcode-parallelism-data' into …
pefontana Jan 23, 2026
3af705e
clippy
pefontana Jan 23, 2026
9303f99
Merge branch 'main' into hardcode-parallelism-data
pefontana Jan 26, 2026
27f4ed5
add parallelism_data.json to Garnix
pefontana Jan 26, 2026
a8d5330
add hardware type to json
pefontana Jan 26, 2026
f265de9
update .json
pefontana Jan 26, 2026
1d3aafd
Fallback: read from /proc/driver/nvidia
pefontana Jan 26, 2026
50762e5
restore scripts/train-solana-test.sh
pefontana Jan 26, 2026
f6c1c7a
update documentation
pefontana Jan 26, 2026
42fd012
Change micro_batch_size for Meta-Llama-3.1 to 1
pefontana Jan 26, 2026
1695b93
nit
pefontana Jan 27, 2026
5175723
look data-parallelism.json in HF repo
pefontana Jan 27, 2026
8f6d14e
change json format
pefontana Jan 28, 2026
6644c67
nvml_wrapper
pefontana Jan 28, 2026
ec28547
Merge branch 'main' into hardcode-parallelism-data
pefontana Jan 28, 2026
fdf4f0d
se tch for GPU count (respects CUDA_VISIBLE_DEVICES)
pefontana Jan 29, 2026
1a454c5
Merge branch 'main' into hardcode-parallelism-data
pefontana Jan 30, 2026
6114eba
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 3, 2026
22f7b26
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 10, 2026
8a4dabd
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 19, 2026
6ac2e70
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 20, 2026
4dc974b
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 23, 2026
6f4ae53
Merge branch 'main' into hardcode-parallelism-data
pefontana Feb 27, 2026
e1f7276
Merge branch 'main' into hardcode-parallelism-data
pefontana Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions architectures/centralized/client/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub async fn build_app(
.await?;

let state_options: RunInitConfig<ClientId, ClientId> = RunInitConfig {
parallelism_auto: p.parallelism_auto,
data_parallelism: p.data_parallelism,
tensor_parallelism: p.tensor_parallelism,
micro_batch_size: p.micro_batch_size,
Expand Down
1 change: 1 addition & 0 deletions architectures/decentralized/solana-client/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub async fn build_app(

let state_options: RunInitConfig<psyche_solana_coordinator::ClientId, NetworkIdentity> =
RunInitConfig {
parallelism_auto: p.parallelism_auto,
data_parallelism: p.data_parallelism,
tensor_parallelism: p.tensor_parallelism,
micro_batch_size: p.micro_batch_size,
Expand Down
3 changes: 2 additions & 1 deletion nix/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ let
|| (builtins.match ".*tests/fixtures/.*$" path != null)
|| (builtins.match ".*.config/.*$" path != null)
|| (builtins.match ".*local-dev-keypair.json$" path != null)
|| (builtins.match ".*shared/client/src/state/prompt_texts/index\\.json$" path != null);
|| (builtins.match ".*shared/client/src/state/prompt_texts/index\\.json$" path != null)
|| (builtins.match ".*shared/client/src/parallelism_data\\.json$" path != null);

src = lib.cleanSourceWith {
src = ../.;
Expand Down
37 changes: 36 additions & 1 deletion psyche-book/src/enduser/create-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,46 @@ run-manager create-run \

At this point, your run has been successfully created.

### Adding parallelism configuration (recommended)

If your run uses a model that is not already in the [parallelism lookup table](https://github.com/PsycheFoundation/psyche/blob/main/shared/client/src/parallelism_data.json), it's recommended to add the optimal parallelism configuration for your model and target GPU hardware. This allows clients to use `PARALLELISM_AUTO=true` for automatic configuration.

#### Option 1: Add to your model's HuggingFace repo (preferred)

Add a `parallelism_data.json` file directly to your model's HuggingFace repository. The client will automatically fetch this configuration at runtime - no Psyche rebuild required.

```json
{
"your-org/your-model": {
"H100": {
"1": { "dp": 1, "tp": 1, "micro_batch_size": 4 },
"8": { "dp": 4, "tp": 2, "micro_batch_size": 4 }
},
"H200": {
"8": { "dp": 8, "tp": 1, "micro_batch_size": 8 }
}
}
}
```

This is the preferred approach because:

- No PR to Psyche required
- No Docker image rebuild needed
- Model creators manage their own configuration
- Changes take effect immediately

#### Option 2: Add to Psyche's compiled table

Alternatively, open a PR to add your model to the [compiled parallelism table](https://github.com/PsycheFoundation/psyche/blob/main/shared/client/src/parallelism_data.json). This is useful for widely-used models that should have default configurations. The format is the same as above.

**Lookup order**: The client first tries to fetch config from the model's HuggingFace repo, then falls back to the compiled table with a warning if not found.

### Initializing configuration

Initially, the run will not have any configuration defined and will remain paused, so no clients can join yet.

To set the run configuration, youll need to provide mostly the same parameters as when creating the run, along with the path to a `config.toml` file that follows the [run config schema](./run-config.md).
To set the run configuration, you'll need to provide mostly the same parameters as when creating the run, along with the path to a `config.toml` file that follows the [run config schema](./run-config.md).

```bash
run-manager update-config \
Expand Down
10 changes: 10 additions & 0 deletions psyche-book/src/enduser/join-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,29 @@ though you might need to.

**`NVIDIA_DRIVER_CAPABILITIES`** - An environment variable that the NVIDIA Container Toolkit uses to determine which compute capabilities should be provided to your container. It is recommended to set it to 'all', e.g. `NVIDIA_DRIVER_CAPABILITIES=all`.

**`PARALLELISM_AUTO`** - Set to `true` to automatically detect optimal parallelism settings based on the model and your GPU hardware.

- When enabled, the client will look up the best `DATA_PARALLELISM`, `TENSOR_PARALLELISM`, and `MICRO_BATCH_SIZE` values from a [built-in configuration table](https://github.com/PsycheFoundation/psyche/blob/main/shared/client/src/parallelism_data.json)
- Your model and GPU hardware combination must be present in the table
- This is the recommended option for most users
- If set, manual parallelism settings below will be ignored

**`DATA_PARALLELISM`** - Number of GPUs to distribute training data across.

- If you have multiple GPUs, you can set this to 2, 4, etc. to speed up training
- If you have 1 GPU, set this to `1`
- Ignored if `PARALLELISM_AUTO=true`

**`TENSOR_PARALLELISM`** - Number of GPUs to distribute the model across, this lets you train a model you can't fit on one single GPU.

- If you have 1 GPU, set this to `1`
- If your have `n` GPUs you can distribute the model across all of them by setting it to `n`.
- Ignored if `PARALLELISM_AUTO=true`

**`MICRO_BATCH_SIZE`** - Number of samples processed per GPU per training step

- Set as high as your GPU memory allows
- Ignored if `PARALLELISM_AUTO=true`

**`AUTHORIZER`** - The Solana address that authorized your wallet to join this run

Expand Down
4 changes: 4 additions & 0 deletions shared/client/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ pub struct TrainArgs {
#[clap(long, env, value_parser = parse_trim_quotes)]
pub run_id: String,

/// Auto-detect parallelism settings from lookup table based on model and GPU count
#[clap(long, env)]
pub parallelism_auto: bool,

#[clap(long, default_value_t = 1, env)]
pub data_parallelism: usize,

Expand Down
1 change: 1 addition & 0 deletions shared/client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod cli;
mod client;
mod fetch_data;
pub mod parallelism_lookup;
mod protocol;
mod state;
mod tui;
Expand Down
40 changes: 40 additions & 0 deletions shared/client/src/parallelism_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"_doc (HuggingFace repo)": {
"gpu type from nvidia-smi": {
"numember of gpus": {
"dp": 0,
"tp": 0,
"micro_batch_size": 0
}
}
},
"emozilla/llama2-20m-init": {
"H100": {
"1": { "dp": 1, "tp": 1, "micro_batch_size": 1 },
"8": { "dp": 1, "tp": 1, "micro_batch_size": 1 }
}
},
"TinyLlama/TinyLlama-1.1B-Chat-v0.4": {
"H100": {
"1": { "dp": 1, "tp": 1, "micro_batch_size": 8 },
"8": { "dp": 8, "tp": 1, "micro_batch_size": 8 }
}
},
"NousResearch/Meta-Llama-3.1-8B": {
"H100": {
"1": { "dp": 1, "tp": 1, "micro_batch_size": 1 },
"8": { "dp": 8, "tp": 1, "micro_batch_size": 4 }
}
},
"deepseek-ai/DeepSeek-V2-Lite": {
"H100": {
"1": { "dp": 1, "tp": 1, "micro_batch_size": 1 },
"8": { "dp": 4, "tp": 2, "micro_batch_size": 2 }
}
},
"NousResearch/Hermes-4-70B": {
"H100": {
"8": { "dp": 1, "tp": 8, "micro_batch_size": 1 }
}
}
}
125 changes: 125 additions & 0 deletions shared/client/src/parallelism_lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use anyhow::Result;
use hf_hub::{Repo, RepoType};
use serde::Deserialize;
use std::collections::HashMap;
use std::process::Command;
use tracing::{info, warn};

const PARALLELISM_DATA: &str = include_str!("parallelism_data.json");
const REMOTE_CONFIG_FILENAME: &str = "parallelism_data.json";

#[derive(Debug, Clone, Copy, Deserialize)]
pub struct ParallelismConfig {
pub dp: usize,
pub tp: usize,
pub micro_batch_size: usize,
}

// Table format: model -> gpu_type -> num_gpus -> config
type Table = HashMap<String, HashMap<String, HashMap<String, ParallelismConfig>>>;

fn get_gpu_type() -> String {
// Try nvidia-smi first
let raw_gpu_name = Command::new("nvidia-smi")
.args(["--query-gpu=name", "--format=csv,noheader"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.and_then(|s| s.lines().next().map(|l| l.trim().to_string()))
.filter(|s| !s.is_empty())
// Fallback: read from /proc/driver/nvidia (works in containers without nvidia-smi)
.or_else(|| {
std::fs::read_dir("/proc/driver/nvidia/gpus")
.ok()?
.filter_map(|e| e.ok())
.next()
.and_then(|entry| {
let info_path = entry.path().join("information");
std::fs::read_to_string(info_path).ok()
})
.and_then(|content| {
content
.lines()
.find(|line| line.starts_with("Model:"))
.map(|line| line.trim_start_matches("Model:").trim().to_string())
})
})
.unwrap_or_default();

// Normalize GPU name to match table keys
if raw_gpu_name.to_uppercase().contains("H200") {
"H200".to_string()
} else if raw_gpu_name.to_uppercase().contains("H100") {
"H100".to_string()
} else {
raw_gpu_name
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you considered using the nvml_wrapper crate instead of shelling out / reading /proc/fs stuff? we can grab gpu count from there too 🤷 and assert that they're all the same GPU for sanity checking :D

we use this in some of the metrics stuff already -
it would be something like:

use nvml_wrapper::Nvml;

#[derive(Debug)]
struct GpuInfo {
    name: String,
    device_count: u32,
}

fn get_gpu_info() -> anyhow::Result<GpuInfo> {
    let nvml = Nvml::init()?;
    let device_count = nvml.device_count()?;

    if device_count == 0 {
        anyhow::bail!("No GPUs found!");
    }

    let mut gpu_names = Vec::new();

    for i in 0..device_count {
        let device = nvml.device_by_index(i)?;
        gpu_names.push(device.name()?);
    }

    let first_name = &gpu_names[0];
    if !gpu_names.iter().all(|name| name == first_name) {
        anyhow::bail!(
            "All GPUs must be of the same type, but we have mismatching names: {:?}",
            gpu_names
        );
    }

    Ok(GpuInfo {
        name: gpu_names.pop().unwrap(),
        device_count,
    })
}

@pefontana pefontana Jan 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Ari!
I add it and works

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed the let device_count = nvml.device_count()?; because was not taking into account CUDA_VISIBLE_DEVICES env var


/// Try to load parallelism config from the model's HuggingFace repo
fn load_from_model_repo(model_repo_id: &str) -> Option<Table> {
let token = std::env::var("HF_TOKEN").ok();

let api = hf_hub::api::sync::ApiBuilder::new()
.with_token(token)
.build()
.ok()?
.repo(Repo::new(model_repo_id.to_string(), RepoType::Model));

let path = api.get(REMOTE_CONFIG_FILENAME).ok()?;
let content = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&content).ok()
}

/// Lookup config in a table
fn lookup_in_table(
table: &Table,
model_repo_id: &str,
gpu_type: &str,
num_gpus: usize,
) -> Option<ParallelismConfig> {
table
.get(model_repo_id)
.and_then(|g| g.get(gpu_type))
.and_then(|n| n.get(&num_gpus.to_string()))
.copied()
}

/// Load the compiled parallelism table
fn load_compiled_table() -> Result<Table> {
serde_json::from_str(PARALLELISM_DATA)
.map_err(|e| anyhow::anyhow!("Failed to parse compiled parallelism data: {}", e))
}

pub fn lookup(model_repo_id: &str) -> Result<ParallelismConfig> {
let num_gpus = tch::Cuda::device_count() as usize;
let gpu_type = get_gpu_type();
info!("Detected {} x {} GPU(s)", num_gpus, gpu_type);

// Try model's own config first
if let Some(table) = load_from_model_repo(model_repo_id) {
if let Some(config) = lookup_in_table(&table, model_repo_id, &gpu_type, num_gpus) {
info!(
"Using parallelism config from model repo '{}'",
model_repo_id
);
return Ok(config);
}
}

// Fall back to compiled table
warn!(
"No parallelism config found in model repo '{}', using compiled defaults",
model_repo_id
);

let table = load_compiled_table()?;
lookup_in_table(&table, model_repo_id, &gpu_type, num_gpus).ok_or_else(|| {
anyhow::anyhow!(
"No config for {} x {} with model '{}'",
num_gpus,
gpu_type,
model_repo_id
)
})
}
Loading