Problem Description – ShardRebalanceError During Model Weight Update
During the CI/CD integration of Qwen‑7B, the distributed training job aborts after a weight checkpoint is pushed. The failure manifests as a ShardRebalanceError indicating that the data partitioning across training nodes is inconsistent. Typical log excerpts look like:
[rank 0] ERROR: ShardRebalanceError: inconsistent partitioning across nodes
[rank 1] RuntimeError: weight update mismatch during shard rebalance (expected shard 3, got shard 2)
torch.distributed.DistBackendError: barrier timeout while synchronizing shards after checkpoint load
ValueError: shard index out of range after weight load – shard map version 5 vs expected version 4
These errors prevent the second stage of the pipeline (validation) from running, causing pipeline failures in GitLab, Jenkins, Azure DevOps, and SageMaker environments.
Root Cause Analysis
The Qwen distributed training stack relies on a shared shard‑map metadata file (shard_index.json) that describes how the model’s weight tensors are partitioned across workers. The root causes identified across multiple incidents are:
- Race condition on shard‑map write: When a checkpoint is saved, node 0 writes a new
shard_index.jsonwhile node 1 is still reading the previous version (see the Jenkins race‑condition incident). - Inconsistent environment variables: An incorrect
RANKorWORLD_SIZEleads each worker to compute a different shard offset, causing mismatched shard indices (Azure DevOps Docker rebuild case). - Filesystem mount divergence: In SageMaker runs, some instances mount a stale NFS cache, so they see an outdated shard‑map version, resulting in a “partition mismatch” error.
- Version skew between checkpoint and runtime: The checkpoint was produced with a different sharding configuration (e.g., changed
tensor_parallel_size) than the current training job, leading to “shard map version X vs expected version Y”.
These observations align with the Qwen Distributed Training and Sharding API Documentation, which states that the shard‑map must be atomically updated and that all workers must load the same version before entering the rebalance barrier.
Investigation and Debugging Steps
1. Capture the failing logs
$ journalctl -u qwen-training.service -n 200
...
[2026-06-08 02:15:12] rank=0 | ShardRebalanceError: inconsistent partitioning across nodes
[2026-06-08 02:15:12] rank=1 | RuntimeError: weight update mismatch during shard rebalance (expected shard 3, got shard 2)
2. Verify shard‑map consistency across nodes
# On each node
cat /mnt/shared/shard_index.json
Expected output (identical on all nodes):
{
"version": 5,
"shards": [
{"rank":0,"tensor_range":"0-1999"},
{"rank":1,"tensor_range":"2000-3999"},
{"rank":2,"tensor_range":"4000-5999"},
{"rank":3,"tensor_range":"6000-7999"}
]
}
If versions differ, the race condition or mount issue is present.
3. Inspect environment variables used by torchrun
echo $RANK $WORLD_SIZE $MASTER_ADDR $MASTER_PORT
All workers must report sequential RANK values from 0 to WORLD_SIZE-1. A mismatch (e.g., two nodes reporting RANK=0) reproduces the Azure DevOps symptom.
4. Check checkpoint metadata
python - <<'PY'
import json, torch
ckpt = torch.load('checkpoint.pt', map_location='cpu')
print(json.dumps(ckpt['shard_meta'], indent=2))
PY
Confirm that shard_meta['tensor_parallel_size'] matches the current training configuration.
5. Reproduce the race condition locally
Run two parallel processes that invoke torch.distributed.barrier() after saving a checkpoint, while deliberately delaying the write of shard_index.json on one process. This helps confirm that the barrier timeout originates from stale metadata.
Resolution – Making Shard Rebalancing Reliable
Step 1 – Serialize shard‑map updates with a lock file
Introduce a filesystem lock (e.g., .shard_index.lock) that workers acquire before writing the new shard map. The lock is released only after all workers have confirmed the write.
# before checkpoint save (executed on rank 0 only)
if [ -f /mnt/shared/.shard_index.lock ]; then
echo "Lock exists, waiting..."
while [ -f /mnt/shared/.shard_index.lock ]; do sleep 1; done
fi
touch /mnt/shared/.shard_index.lock
# Save checkpoint and new shard map
python save_checkpoint.py --output /mnt/shared/checkpoint.pt
python generate_shard_map.py --output /mnt/shared/shard_index.json
rm /mnt/shared/.shard_index.lock
Step 2 – Enforce consistent environment variables in CI scripts
Update the CI job template to derive RANK from the orchestrator (GitLab Runner, Jenkins, Azure Pipelines) rather than relying on container defaults.
# CI template snippet
export WORLD_SIZE=${CI_NODE_TOTAL}
export RANK=${CI_NODE_INDEX}
export MASTER_ADDR=${CI_MASTER_IP}
export MASTER_PORT=29500
torchrun --nproc_per_node=$WORLD_SIZE \
--master_addr=$MASTER_ADDR --master_port=$MASTER_PORT \
train.py
Step 3 – Use a versioned shard‑map directory
Store each shard map under a versioned subdirectory (shard_map/v5/shard_index.json) and pass the version to the training script. Workers verify that the directory exists before proceeding.
# train.py excerpt
import os, json, torch.distributed as dist
shard_dir = os.getenv('SHARD_MAP_DIR', '/mnt/shared/shard_map')
assert os.path.isdir(shard_dir), f"Shard map directory {shard_dir} missing"
with open(os.path.join(shard_dir, 'shard_index.json')) as f:
shard_meta = json.load(f)
print(f"[rank={dist.get_rank()}] Loaded shard map version {shard_meta['version']}")
Step 4 – Add a post‑load barrier with timeout handling
After loading a checkpoint, explicitly call dist.barrier() and catch DistBackendError. If a timeout occurs, abort and retry the checkpoint load.
try:
dist.barrier(timeout=300)
except torch.distributed.DistBackendError as e:
logger.error("Barrier timeout after checkpoint load: %s", e)
raise RuntimeError("ShardRebalanceError: barrier timeout")
Verification – Confirming the Fix
- Run a dry‑run CI job that only executes the checkpoint save and load steps. Verify that all workers print the same shard map version.
- Check for absence of ShardRebalanceError in the job logs.
- Validate training continuity by allowing the job to proceed to the validation stage and confirming that the final accuracy metrics match the previous successful run.
- Monitor the lock file – ensure it is created and removed exactly once per checkpoint cycle.
Sample successful log excerpt:
[2026-06-08 02:20:45] rank=0 | Loaded shard map version 6
[2026-06-08 02:20:45] rank=1 | Loaded shard map version 6
[2026-06-08 02:20:45] INFO: All workers passed shard rebalance barrier
[2026-06-08 02:21:10] INFO: Training step 500 completed, validation accuracy 84.3%
Operational Experience – Lessons Learned
- Misleading symptom: The error often appears as a generic “torch.distributed barrier timeout,” leading engineers to suspect network issues rather than shard‑map inconsistency.
- Assumption that NFS is instantly consistent: In SageMaker and Azure, NFS caches can delay propagation of the new
shard_index.json. Using a lock file forces explicit synchronization. - Version drift after CI image rebuild: A subtle change in the Docker base image altered the default
tensor_parallel_size, causing a mismatch between checkpoint metadata and runtime configuration. - Race condition hidden in CI parallelism: When multiple pipelines share the same checkpoint storage, they can interfere with each other’s shard maps. Isolating checkpoint directories per pipeline run eliminates cross‑pipeline contamination.
Best Practices and Prevention
| Practice | Why it matters | Implementation tip |
|---|---|---|
| Atomic shard‑map updates | Prevents stale metadata from being read by any worker | Use a lock file or atomic rename (write to shard_index.tmp then mv) |
| Explicit environment variable propagation | Ensures each worker computes the same shard offset | Set RANK and WORLD_SIZE from CI orchestrator variables |
| Versioned checkpoint directories | Avoids cross‑run contamination | Structure storage as /checkpoints/run_ |
| Post‑load barrier with retry logic | Detects and recovers from transient sync failures | Wrap dist.barrier() in a try/except with exponential backoff |
| Monitoring of shard‑map health | Early detection of divergence before training aborts | Emit a custom metric shard_map_version to Prometheus from each rank |
FAQ – Common Follow‑Up Questions
- Why does the error only appear after a checkpoint is uploaded? The checkpoint load triggers a rebalance barrier that re‑reads the shard‑map. If the map was updated concurrently or is outdated on any node, the barrier detects a mismatch and aborts.
- Can I disable shard rebalance to avoid the error? Disabling the barrier sacrifices weight consistency and can lead to silent corruption. The recommended approach is to fix the underlying synchronization, not to skip the rebalance.
- How do I verify which shard map version each worker loaded? Insert a log line after loading the JSON (see the
printstatement intrain.py) or emit a custom metric from each rank. - Is the issue related to the Qwen model parallelism guide? Yes. The guide (Model Parallelism Guide) emphasizes that all workers must share identical
shard_metabefore entering any collective operation. - What should I do if I still see “partition mismatch” after applying the lock? Check that all filesystem mounts are truly shared (e.g., verify
df -hshows the same NFS source) and confirm that no stale processes are holding old lock files.
Related Topic Hub: LLM Systems Troubleshooting Hub