Basics¶
In this guide, we’ll cover the basic terminology associated with the Lightning framework.
Lightning App¶
The LightningApp
runs a tree of one or more components that interact to create end-to-end applications. There are two kinds of components: LightningFlow
and LightningWork
. This modular design enables you to reuse components created by other users.
Lightning Work¶
The LightningWork
component is a building block optimized for long-running jobs or integrating third-party services. LightningWork can be used for training large models, downloading a dataset, or any long-lasting operation.
Lightning Flow¶
The LightningFlow
component coordinates long-running tasks LightningWork
and runs its children LightningFlow
components.
Lightning App Tree¶
Components can be nested to form component trees where the LightningFlows are its branches and LightningWorks are its leaves.
Here’s a basic application with four flows and two works:
from lightning.app import LightningFlow, LightningApp
from lightning.app.testing import EmptyFlow, EmptyWork
class FlowB(LightningFlow):
def __init__(self):
super().__init__()
self.flow_d = EmptyFlow()
self.work_b = EmptyWork()
def run(self):
...
class FlowA(LightningFlow):
def __init__(self):
super().__init__()
self.flow_b = FlowB()
self.flow_c = EmptyFlow()
self.work_a = EmptyWork()
def run(self):
...
app = LightningApp(FlowA())
And here’s its associated tree structure:
A Lightning App runs all flows into a single process. Its flows coordinate the execution of the works each running in their own independent processes.
Lightning Distributed Event Loop¶
Drawing inspiration from modern web frameworks like React.js, the Lightning app runs all flows in an event loop (forever), which is triggered every 0.1 seconds after collecting any works’ state change.

When running an app in the cloud, the LightningWork
run on different machines. Lightning communicates any LightningWork
state changes to the event loop which re-executes the flow with the newly-collected works’ state.
Lightning App State¶
By design, each component is stateful and its state is composed of all its attributes. The Lightning App State is the collection of all its components state.
With this mechanism, any component can react to any other component state changes, simply by relying on its attributes within the flow.
For example, here we define two flow components, RootFlow and ChildFlow, where the child flow prints and increments a counter indefinitely and gets reflected in RootFlow state.
You can easily check the state of your entire app:
from lightning.app import LightningWork, LightningFlow, LightningApp
from lightning.app.utilities.app_helpers import pretty_state
class Work(LightningWork):
def __init__(self):
super().__init__(cache_calls=False)
# Attributes are registered automatically in the state.
self.counter = 0
def run(self):
# Incrementing an attribute gets reflected in the `Flow` state.
self.counter += 1
class Flow(LightningFlow):
def __init__(self):
super().__init__()
self.w = Work()
def run(self):
if self.w.has_started:
print(f"State: {pretty_state(self.state)} \n")
self.w.run()
app = LightningApp(Flow())
Here’s the entire tree structure associated with your app:
And here’s the output you get when running the above application using Lightning CLI:
$ lightning_app run app docs/source/code_samples/quickstart/app_01.py
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
State: {'works': {'w_1': {'vars': {'counter': 1}}, 'w_2': {'vars': {'counter': 0}}}}
State: {'works': {'w_1': {'vars': {'counter': 3}}, 'w_2': {'vars': {'counter': 1}}}}
State: {'works': {'w_1': {'vars': {'counter': 4}}, 'w_2': {'vars': {'counter': 1}}}}
State: {'works': {'w_1': {'vars': {'counter': 5}}, 'w_2': {'vars': {'counter': 2}}}}
State: {'works': {'w_1': {'vars': {'counter': 6}}, 'w_2': {'vars': {'counter': 2}}}}
State: {'works': {'w_1': {'vars': {'counter': 7}}, 'w_2': {'vars': {'counter': 3}}}}
...
This app will count forever because the lightning event loop indefinitely calls the root flow run method.
Controlling the Execution Flow¶
LightningWork: To Cache or Not to Cache Calls¶
With Lightning, you can control how to run your components.
By default, the LightningFlow
is executed infinitely by the Lightning Infinite Loop and the LightningWork
does not run in parallel,
meaning the Lightning Infinite Loop (a.k.a the flow) waits until that long-running work is completed to continue.
Similar to React.js Components and Props, the LightningWork
component accepts arbitrary inputs (the “props”) to its run method and by default runs once for each unique input provided.
Here’s an example of this behavior:
from lightning.app import LightningWork
class ExampleWork(LightningWork):
def run(self, *args, **kwargs):
print(f"I received the following props: args: {args} kwargs: {kwargs}")
work = ExampleWork()
work.run(value=1)
# Providing the same value. This won't run as already cached.
work.run(value=1)
work.run(value=1)
work.run(value=1)
work.run(value=1)
# Changing the provided value. This isn't cached and will run again.
work.run(value=10)
And you should see the following by running the code above:
$ python example.py
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
# After you have clicked `run` on the UI.
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 10}
As you can see, the intermediate run didn’t execute as already cached.
To disable this behavior, set cache_calls=False
to make any LightningWork run infinitely.
--- /home/docs/checkouts/readthedocs.org/user_builds/pytorch-lightning/checkouts/20036/docs/source-app/code_samples/basics/0.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/pytorch-lightning/checkouts/20036/docs/source-app/code_samples/basics/1.py
@@ -2,6 +2,9 @@
class ExampleWork(LightningWork):
+ def __init__(self):
+ super().__init__(cache_calls=False)
+
def run(self, *args, **kwargs):
print(f"I received the following props: args: {args} kwargs: {kwargs}")
$ python example.py
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
# After you have clicked `run` on the UI.
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 1}
I received the following props: args: () kwargs: {'value': 10}
Note
Passing a sequence of different props to the work run method queues their execution. We recommend avoiding this behavior as it can be hard to debug. Instead, wait for the previous run to execute.
LightningWork: Parallel vs Non Parallel¶
The LightningWork component is made for long-running jobs.
As an example, let’s create a long-running LightningWork component that will take 1 hour to do its “work”.
from time import sleep
from lightning.app import LightningWork, LightningFlow, LightningApp
# This work takes an hour to run
class HourLongWork(LightningWork):
def __init__(self, parallel: bool = False):
super().__init__(parallel=parallel)
self.progress = 0.0
def run(self):
self.progress = 0.0
for _ in range(3600):
self.progress += 1.0 / 3600 # Reporting my progress to the Flow.
sleep(1)
class RootFlow(LightningFlow):
def __init__(self, child_work: LightningWork):
super().__init__()
self.child_work = child_work
def run(self):
# prints the progress from the child work
print(round(self.child_work.progress, 4))
self.child_work.run()
if self.child_work.counter == 1.0:
print("1 hour later!")
app = LightningApp(RootFlow(HourLongWork()))
Here’s the output you get when running the above application using Lightning CLI:
$ lightning_app run app docs/source/code_samples/quickstart/app_02.py
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
# After you have clicked `run` on the UI.
0.0 0.0
...
0.0003 0.0003
...
1.0 1.0
...
1 hour later!
1.0 1.0
1 hour later!
1.0 1.0
1 hour later!
...
The child work runs only once, hence why the progress counter stops increasing once the work is completed.
This is useful for monitoring the progress of a long-running operation, like training a big model.
Note
The Lightning Infinite Loop runs multiple cycles per second. It is good practice to keep the loop running fast, so that your application stays responsive, especially when it contains user-interface components.
Multiple works¶
In practical use cases, you might want to execute multiple long-running works in parallel.
To enable this behavior, set parallel=True
in the __init__
method of
your LightningWork
.
Here’s an example of the interaction between parallel and non-parallel behaviors:
Below, we reuse the HourLongWork work defined in the previous example, but modify the RootFlow to run two HourLongWork works in a parallel way.
from lightning.app import LightningWork, LightningFlow, LightningApp
from docs.quickstart.app_02 import HourLongWork
class RootFlow(LightningFlow):
def __init__(self, child_work_1: LightningWork, child_work_2: LightningWork):
super().__init__()
self.child_work_1 = child_work_1
self.child_work_2 = child_work_2
def run(self):
print(round(self.child_work_1.progress, 4), round(self.child_work_2.progress, 4))
self.child_work_1.run()
self.child_work_2.run()
if self.child_work_1.progress == 1.0:
print("1 hour later `child_work_1` started!")
if self.child_work_2.progress == 1.0:
print("1 hour later `child_work_2` started!")
app = LightningApp(RootFlow(HourLongWork(parallel=True), HourLongWork(parallel=True)))
Above, both child_work_1
and child_work_2
are long-running works that are executed
asynchronously in parallel.
When running the above app, we see the following logs:
$ lightning_app run app docs/source/code_samples/quickstart/app/app_0.py
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
# After you have clicked `run` on the UI.
0.0, 0.0
...
0.0003, 0.0003
...
1.0, 1.0
...
1 hour later `child_work_1` started!
1 hour later `child_work_2` started!
0.0, 0.0
...
0.0003, 0.0003
...
1.0, 1.0
1 hour later `child_work_1` started!
1 hour later `child_work_2` started!
...
Next Steps¶
To keep learning about Lightning, build a UI and Frontends.