-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Holoscan not running multiple instances of operator simultaneously #41
Comments
Hi there @CameronDevine , thanks for using Holoscan SDK. Yes, by default Holoscan SDK uses greedy scheduling, but also provides a polling-based scheduler and an event-based scheduler for multithreaded operations. Recommend visiting the Holoscan Schedulers documentation or multithreaded scheduler example for more on multithreaded approaches. |
Hi @tbirdso, here's some code showing the issue I am encountering. When run with from holoscan.core import Application, Operator
import holoscan.schedulers
import argparse
from time import sleep, monotonic
class Count(Operator):
def setup(self, spec):
spec.output("out")
def start(self):
self.i = 0
def compute(self, input, output, context):
self.i += 1
output.emit(self.i, "out")
class Wait(Operator):
def setup(self, spec):
spec.input("in")
spec.output("out")
spec.param("duration")
def compute(self, input, output, context):
sleep(self.duration)
output.emit(input.receive("in"), "out")
class Rate(Operator):
def setup(self, spec):
spec.input("in")
spec.param("window")
def start(self):
self.last_call = None
self.durations = []
def compute(self, input, output, context):
if self.last_call is None:
self.last_call = monotonic()
print(f"Received {input.receive('in')}")
else:
now = monotonic()
self.durations.append(now - self.last_call)
self.last_call = now
self.durations = self.durations[-self.window :]
rate = len(self.durations) / sum(self.durations)
print(f"Received {input.receive('in')} (rate {rate} Hz)")
class Test(Application):
def __init__(self, scheduler):
super().__init__()
schedulers = {
"greedy": lambda: holoscan.schedulers.GreedyScheduler(self),
"event": lambda: holoscan.schedulers.EventBasedScheduler(
self, worker_thread_number=4
),
"multi": lambda: holoscan.schedulers.MultiThreadScheduler(
self, worker_thread_number=4
),
}
assert scheduler in schedulers
self.scheduler(schedulers[scheduler]())
def compose(self):
count = Count(self)
wait = Wait(self, duration=2)
rate = Rate(self, window=8)
self.add_flow(count, wait)
self.add_flow(wait, rate)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--scheduler",
"-s",
type=str,
default="greedy",
help='The scheduler to use, either "greedy", "event", or "multi".',
)
args = parser.parse_args()
app = Test(args.scheduler)
app.run()
if __name__ == "__main__":
main() |
Hi @CameronDevine, Thanks for providing the concrete code example. I think there is some misconception in how the scheduling works. Based on your compose method def compose(self):
count = Count(self)
wait = Wait(self, duration=2)
rate = Rate(self, window=8)
self.add_flow(count, wait)
self.add_flow(wait, rate) There are only three total operators connected in a single linear chain, so there is nothing to potentially run in parallel. In other words, the scheduler does not launch multiple copies of the application. There are only these three operators that get repeatedly executed. For a concrete example of running multiple wait/delay operators in parallel see this one included in the examples folder: In that case, there is a single source operator that cannects to N "delay" operators in parallel which then converge on a single sink operator. It is not require to have a common source/sink, that is just what we happened to choose for the example. You could also have the same type of example, but with a separate source/sink for each "delay" operator. |
@CameronDevine how many of these operators will you have connected serially in your use case? In this simple xample, there is only one operator that takes a lot of time and the runtime in the other operators are trivial so it won't help even if you have multiple workers. If we modify your example here to include multiple operators that are connected serially, we could see a difference between the reported rate. Here I have set the worker_thread_number count to 2.
With greedy scheduler:
With event based scheduler:
|
Hi @whom3, I expect I will have about 10 operators, which is still lower than the number of cores of the systems I will be running the code on. |
@CameronDevine You could try the following which modifies your original example such that
For the above, I am seeing much better scaling when increasing the # of workers. |
Please describe your question
I have an application where I would like to use Holoscan for an image processing pipeline. The problem I am having is that Holoscan seems to only run a single instance of an operator at a time. This is in contrast to Intel's Thread Building Blocks which will run multiple copies of an operator at once. As much of the processing I need to do is serial, this results in the performance of the application being dominated by the runtime of the slowest operator. Is there a different scheduler I can use to enable multiple copies of an operators to be run at once, or could I implement a custom scheduler?
Please specify what Holoscan SDK version you are using
2.8.0
Please add any details about your platform or use case
I am working on building a pipeline for real time processing of mouse brain images from transmission electron microscopes.
The text was updated successfully, but these errors were encountered: