Hi team, Was wondering if I could kindly ask for ...
# ask-metaflow
a
Hi team, Was wondering if I could kindly ask for some assistance. I'm working on a demo for LLM Fine Tuning using PyTorch Lightning that leverages Metaflow's
@pytorch_parallel
decorator. I'm still rather new to distributed training, so learning the ropes. I've tried
fsdp
to do sharded training, but it was having some trouble with the model parameters. I was getting this error -
valueerror: optimizer got an empty parameter list
. When I printed out the params though, they're definitely there. I also tried
ddp
as the strategy, which didn't give any explicit error, but it just stalled and didn't have any progress output in the stdout logs... I have a reproducible example here - https://github.com/rileyhun/llm_finetuning_metaflow/blob/main/gpt-j-8bit-flow.py. Any pointers or guidance would be greatly appreciated.
โœ… 1
h
hey! few quick questions. I see you are using PTL 1.8.6. And you are using
@pytorch_parallel
so you have multiple Jobs spawned with gang scheduling to run the distributed training (over multiple nodes (with multiple GPUS per node?). 1. Did other tasks start in the image you shared. AWS batch can take up some time to start all the nodes you need and then place the containers on the nodes. Can you verify if other tasks started coz it seems the "control task" is waiting on the child tasks to start and stuck in the barrier. "control task" is the main that that will get the MASTER_IP and to which other tasks will communicate with. 2. I think the DDP support for PT Lightning has changed since we built the
@pytorch_parallel
. This short version is that now it calls the parent script under the hood on its own and it also has introduced abstractions to handle how a ClusterEnviornment performs DDP or SFDP. you can try using LightningEnvironment if that helps? `ClusterEnvironment`s can be passed to PT lightning Trainer as a plugins.
a
Hi @little-apartment-49355, Thanks for your response. I am using 16 nodes, each with just 1 GPU (
g5.4xlarge
), and they all started up and were running. I can look into
ClusterEnvironment
Added the following:
Copy code
env = LightningEnvironment()
        env.world_size = lambda: int(current.parallel.node_index)
        env.global_rank = lambda: int(current.parallel.num_nodes)

ddp = DDPStrategy(
              find_unused_parameters=True,
              cluster_environment=env, 
              process_group_backend="gloo",
              accelerator="gpu"
            )
            
trainer = pl.Trainer(
                log_every_n_steps=1,
                devices=self.num_gpus,
                num_nodes=self.num_parallel,
                max_epochs=config.num_epochs,
                deterministic=True,
                enable_checkpointing=True,
                enable_model_summary=True,
                profiler="simple",
                precision=16,
                callbacks=[TQDMProgressBar(refresh_rate=0)],
                strategy=ddp
            )

trainer.fit(finetuner)
But looks like it is still stalling without any logs in stdout
c
One thing I am curious about, given the
valueerror: optimizer got an empty parameter list
and DDP issue is the
Adam8bit
parts. I wonder if setting this arg when HuggingFace loads model will help:
Copy code
self.model = GPTJForCausalLM.from_pretrained(..., load_in_8bit=True)
I see in the DDP version of the flow output it is using 16bit AMP, so maybe the optimizer is getting confused and not reading the optimizer state correctly since the instantiated Adam is 8bit. FYI I haven't had a chance to reproduce yet, so this is just a guess.
a
Thanks Eddie! Good catch. I'll add that in and see if that helps.
I'm getting an error:
Copy code
TimeoutError: The client socket has timed out after 1800s while trying to connect to (10.14.52.26, 51371).
h
Hey Riley what are you network and VPC settings. It seems the jobs are unable to communicate with each other.
a
Hi @hallowed-glass-14538, I think the network settings should be OK. I tried a simple pytorch distributed program using the MNIST data and that seemed to work.
h
Can you change the backend from nccl to
gloo
and try it out ? I noticed a comment in our code that says that nccl didn't play nice with AWS batch.
a
Ok, will do - I sort of gave up and switched models now (I just need this for a DEMO ๐Ÿ˜…), so I'm working on pre-training a BERT model right now instead and *cross fingers...*I think it might be working now. I think I had to set these environment variables -
Copy code
os.environ["MASTER_PORT"] = "12345"
os.environ["MASTER_ADDR"] = "localhost"
os.environ["WORLD_SIZE"] = str(current.parallel.num_nodes)
os.environ["NODE_RANK"] = str(current.parallel.node_index)
I also removed the cluster environment/lightning environment.
๐Ÿคž 1
h
Wait so if there are 16 machines then the master should have a different IP than localhost
you can even use
current.parallel.main_ip
to set that IP
thankyou 1
a
Oh very nice! Thanks for the warning Valay. Let me plug that in. ๐Ÿ™
h
and
master_port
can be set in
@pytorch_parallel
. So something like :
@pytorch_parallel(master_port=9001)
a
Okay got it. Will fix that too.
Two of the nodes are failing. I'll try
gloo
backend as you suggested.
With gloo, getting a different error from the nodes; World size is set to the number of nodes though, so that's kind of odd:
Copy code
RuntimeError: [enforce fail at /opt/conda/conda-bld/pytorch_1656352645774/work/third_party/gloo/gloo/context.cc:27] rank < size. 1 vs 1
Copy code
RuntimeError: [enforce fail at /opt/conda/conda-bld/pytorch_1656352645774/work/third_party/gloo/gloo/context.cc:27] rank < size. 2 vs 1
Copy code
RuntimeError: [enforce fail at /opt/conda/conda-bld/pytorch_1656352645774/work/third_party/gloo/gloo/context.cc:27] rank < size. 3 vs 1
h
what is the value of
devices
and
num_nodes
in your
Trainer
?
a
devices
is 1 and
num_nodes
is 4
h
can you try setting
LOCAL_RANK=0
once
a
Yup.
Copy code
os.environ["MASTER_ADDR"] = str(current.parallel.main_ip)
os.environ["WORLD_SIZE"] = str(current.parallel.num_nodes)
os.environ["NODE_RANK"] = str(current.parallel.node_index)
os.environ["LOCAL_RANK"] = 0
Should I set a
GLOBAL_RANK
as well?
Unfortunately, still getting those rank errors
h
can you print
os.environ
in your task processes get get all the actual values of
MASTER_ADDR
,
GLOBAL_RANK
etc. I wanted to ensure that we are not modifying something from the MF side at runtime. can you also share your code by any chance ? Wanted to see all configurations once.
a
Here's my reproducible code example: https://github.com/rileyhun/llm_finetuning_metaflow/blob/main/fsdp/fsdp_flow.py I'm currently trying it with a Lightning Environment to see if that helps
h
can you change here to:
Copy code
env.global_rank = lambda: int(current.parallel.node_index)
a
Yup - I think it has to be a
str
though otherwise it errors out.
h
sure make it
str
.
currently it is setting global rank as the number of nodes. We want it to be the node index
a
Ah ok. Will try it now
Doesn't like the Lightning Environment -
Copy code
RuntimeError: [/opt/conda/conda-bld/pytorch_1656352645774/work/third_party/gloo/gloo/transport/tcp/pair.cc:598] Connection closed by peer [10.14.52.10]:5558
Also got this error -
Copy code
ValueError: Invalid rank 1, rank should be in the interval [0, 0]
h
can you print out your PT related env vars here ?
did you set
master_port=9001
when you called this ?
a
Printing them out on the next flow:
Copy code
os.environ["MASTER_ADDR"] = str(current.parallel.main_ip)
env = LightningEnvironment()
env.world_size = lambda: int(current.parallel.num_nodes)
env.global_rank = lambda: int(current.parallel.num_nodes)
env.node_rank = lambda: int(current.parallel.node_index)
env.local_rank = lambda: int(os.environ.get("LOCAL_RANK", 0))
        
print(os.environ)
print(env)
And yup I did
Copy code
@pytorch_parallel(master_port=9001)
h
coz it seems that its calling
[10.14.52.10]:5558
so may even have to pluck and replace the main_port property
a
ah ok. Got it.
h
and just return the port you are opening
a
environ variables:
Copy code
'MF_PARALLEL_MAIN_IP': '10.14.51.206', 'MF_PARALLEL_NUM_NODES': '4', 'MF_PARALLEL_NODE_INDEX': '0', 'MASTER_PORT': '9001', 'MASTER_ADDR': '10.14.51.206', 'NODE_RANK': '0', 'WORLD_SIZE': '4', 'NUM_NODES': '4', 'PL_TORCH_DISTRIBUTED_BACKEND': 'gloo', 'PYTORCH_NVML_BASED_CUDA_CHECK': '1', 'CRC32C_SW_MODE': 'auto'
No luck - still getting
Copy code
ValueError: Invalid rank 2, rank should be in the interval [0, 0]
c
I think this error indicates
torch.distributed
sees worldsize as 1. Does this specific error go away if you uncomment the line:
Copy code
os.environ["WORLD_SIZE"] = str(current.parallel.num_nodes)
a
Let me try it @crooked-jordan-29960. Thanks so much for all the help by the way guys. Really appreciate it. ๐Ÿ™
c
of course, we appreciate you putting these feature through their paces!
a
To clarify, I'm using a
LightningEnvironment
to set up the cluster config: commenting out
world_size
Copy code
os.environ["MASTER_ADDR"] = str(current.parallel.main_ip)
os.environ["MASTER_PORT"] = "9001"
        
env = LightningEnvironment()
# env.world_size = lambda: int(current.parallel.num_nodes)
env.global_rank = lambda: int(current.parallel.node_index)
env.node_rank = lambda: int(current.parallel.node_index)
env.local_rank = lambda: int(os.environ.get("LOCAL_RANK", 0))

fsdp_native = DDPFullyShardedNativeStrategy(
            cpu_offload=CPUOffload(offload_params=True),
            cluster_environment=env,
            process_group_backend="gloo"
        )
c
ahh I see, and actually I also now see that var is already in your list above, so something else must be off in the chain of events that launches the underlying torch.distributed job from the lightning Trainer/Environment ๐Ÿค”
a
I'm afraid commenting it out doesn't work -
Copy code
RuntimeError: [enforce fail at /opt/conda/conda-bld/pytorch_1656352645774/work/third_party/gloo/gloo/context.cc:27] rank < size. 8 vs 1
h
can you use this as your
ClusterEnvironment
:
Copy code
import logging
import os
from metaflow import current

from lightning.fabric.plugins.environments.cluster_environment import ClusterEnvironment

log = logging.getLogger(__name__)


class MetaflowEnvironment(ClusterEnvironment):
    """
    Quick Dirty MF environment in PTL for Single GPU Multi-node training.
    """

    @property
    def creates_processes_externally(self) -> bool:
        return True

    @property
    def main_address(self) -> str:
        return current.parallel.main_ip

    @property
    def main_port(self) -> int:
        return 9001 # Fix me

    @staticmethod
    def detect() -> bool:
        return True

    def world_size(self) -> int:
        return int(current.parallel.num_nodes)

    def set_world_size(self, size: int) -> None:
        log.debug("MetaflowEnvironment.set_world_size was called, but setting world size is not allowed. Ignored.")

    def global_rank(self) -> int:
        return int(current.parallel.node_index)

    def set_global_rank(self, rank: int) -> None:
        log.debug("MetaflowEnvironment.set_global_rank was called, but setting global rank is not allowed. Ignored.")

    def local_rank(self) -> int:
        return 0

    def node_rank(self) -> int:
        return self.global_rank()
I wanna see if this fixes things.
a
Yes - will do. Thanks @hallowed-glass-14538 !
Nope no luck ๐Ÿ˜ญ
Copy code
ValueError: Invalid rank 10, rank should be in the interval [0, 0]
h
Let me have a look in the morning. Has to be something really tiny that we need to change to fix all of this ๐Ÿ˜„
a
Okay thanks very much! thankyou
h
can you also put the stacktrace of the error for debugging
Is this the only logs ? can you share logs from all tasks including control task ?
a
@hallowed-glass-14538 - I tried this with
ddp
instead, and it worked!
๐ŸŽ‰ 2
Must be an issue with
fsdp
integration, perhaps?
h
Ok, based on the all the data I have some good news. 1. The bottleneck is not MF and connectivity, but the dataloader and how the sampler works. 2. The error comes from the distributed sampler and how PTL wraps the distributed sampler. 3. You are not using a distributed sampler and my hunch is that you will have to feed a DistributedSampler from top level to the DataLoaders (based on the FSDP tutorial)
So overall the issue is not in the FSDP integration but rather how we are formulating the
DataLoader
; I think if we customize the dataloader a little to use distributed sampling from the top-level then we should be even able to support FSDP
Signing off now! Glad it worked out for you ๐Ÿ˜„
thankyou 1
a
Quick question about this - doesn't PyTorch Lightning wrap the
DataLoader
in a distributed sampler already?
h
Yes it does wrap it but somehow right now its passing the global rank to the sampler instead of the local rank. I don't know the exact code path its taking to pass the rank to the sampler but seems that ever strategy has a way to set the sampler's kwargs. Since DDP worked without even modifying anything else i assume its passing the correct rank to the sampler. I think you can try to instantiate the sampler yourself and pass it to the dataloader for FSDP or try going down the rabbithole of figuring out what kwargs are set via FSDP .
a
Got it - good to know. Thanks @hallowed-glass-14538. Will definitely investigate that. Another question - is there anything that can be done to use
nccl
as the process group backend? I don't think
gloo
is recommended for communication between GPUs, correct me if I'm wrong?
h
yes yes. nccl is way better but when we were developing this we noticed aws batch was not playing very nice with nccl. Currently @parallel only supports batch so we haven't researched or explored on paths to support nccl.
a
Update from my side -
fsdp
wasn't working unless process group is using
nccl
backend. But the
DistributedSampler
worked I believe because it got me out of that
rank
issue I was experiencing. This is the new error I was getting:
Copy code
RuntimeError: no support for _allgather_base in Gloo process group
I did some research and currently looking into whether disabling IOMMU will help get NCCL working with AWS Batch.
Hi @hallowed-glass-14538 @flat-television-23413 - A mix of good and bad news - I think I solved the NCCL process group error, so the
@parallel
decorator works nicely with NCCL now! I tried this w/ DDP and worked like a charm. I basically added this
env
variable:
Copy code
"NCCL_SOCKET_IFNAME": "eth0"
The bad news is that I don't think FSDP is working as it's supposed to. Using DDP with NCCL process group, it only took 3 minutes to train. Super fast! But FSDP just hangs. No networking error, no rank error, or any error for that matter. Just hanging for about 1.5hrs... My
DistributedSampler
looks like this -
Copy code
train_sampler = DistributedSampler(
            train_dataset,
            rank=current.parallel.node_index,
            num_replicas=current.parallel.num_nodes,
            shuffle=True
        )
        
        train_kwargs = {
            "batch_size": self.batch_size,
            "sampler": train_sampler,
            "num_workers": 1,
            "shuffle": False,
            "drop_last": True,
            "pin_memory": True
        }

        train_loader = DataLoader(
            train_dataset, **train_kwargs
        )
        
        val_sampler = DistributedSampler(
            val_dataset,
            rank=current.parallel.node_index,
            num_replicas=current.parallel.num_nodes,
            shuffle=True
        )
        
        val_kwargs = {
            "batch_size": self.batch_size,
            "sampler": val_sampler,
            "num_workers": 1,
            "shuffle": False,
            "drop_last": True,
            "pin_memory": True
        }

        val_loader = DataLoader(
            val_dataset, **val_kwargs
        )
        
        test_sampler = DistributedSampler(
            test_dataset,
            rank=current.parallel.node_index,
            num_replicas=current.parallel.num_nodes,
            shuffle=True
        )
        
        test_kwargs = {
            "batch_size": self.batch_size,
            "sampler": test_sampler,
            "num_workers": 1,
            "shuffle": False,
            "drop_last": True,
            "pin_memory": True
        }

        test_loader = DataLoader(
            test_dataset, **test_kwargs
        )
Any guidance or advice?
h
Well done riley on NCCL support !
thankyou 1
this is even a great learning for us
๐Ÿ’ฏ 1
๐Ÿ‘ 1
This maybe a long shot but I noticed you were running an older version of PTL / PT; It is possible to run FSDP for the latest version of PTL/PT ? One more question. Did you come across this GH issue on the topic ? Can you also check docs about this ?
a
Oh interesting! Thanks so much. I will give that a try and report back. I could upgrade PTL but upgrading PyTorch beyond
1.12
in my
@conda_base
seems to result in the training session not being able to find cuda installed. So I just kept it at
1.12
.
Okay somewhat good news! FSDP seemed to work when I remove
CPUOffload
for the params. Although, I'm not sure why the model summary looks like this. Seems like it can't detect the params?
Short context is that PT distributed training strategies wrap around a model object. Maybe fsdp is not playing nice with the model object somehow ?
Making the summary come out wrong .
This is a hunch based on my reading. But need to look deeper though
a
Ahh maybe. I'm downgrading to PL 1.7.7 to see if that helps - https://github.com/Lightning-AI/lightning/discussions/16402 Not a huge deal either way. Still want to figure out why CPU Offload results in significant hanging...
h
And by โ€œnot playing niceโ€ I mean itโ€™s not able to find the attributes seeked by summary function
a
Gotcha. Makes total sense.
Okay downgrading to PL 1.7.7 resolved the model summary issue. The params are shown again. But now getting a new error -
Copy code
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cpu and cuda:0! (when checking argument for argument index in method wrapper__index_select)
lol. This is never-ending.
๐Ÿ˜‚ 2
Overall though, looks like
@pytorch_parallel
decorator is working really well and doing what it needs to be doing! This is great! Super excited!
โค๏ธ 1