Top-Level Core Concept: Module
A Module is the top-level component in LazyLLM and one of its core concepts. A Module possesses four key capabilities: training, deployment, inference, and evaluation. Each Module can choose to implement some or all of these capabilities, and each capability can be composed of one or more functions, Components, or other Modules. In this chapter, we will provide a detailed introduction to the usage of Modules.
API Reference
You can refer to the Module's API documentation module
Defining a Module (Module
)
By inheriting
To define a Module
, you simply need to create a custom class that inherits from lazyllm.module.ModuleBase
. The custom module needs to implement at least one of the following three methods:
_get_train_tasks
: Defines training/fine-tuning tasks, returns a training/fine-tuning taskpipeline
, and executes the tasks when theupdate
method is called._get_deploy_tasks
: Defines deployment tasks, returns a deployment taskpipeline
, and executes deployment tasks when thestart
method is called; or after executing training tasks when theupdate
method is called.forward
: Defines the specific execution process of theModule
, which will be called byModule.__call__.
Here is an example:
>>> import lazyllm
>>> class MyModule(lazyllm.module.ModuleBase):
...
... def __init__(self, name, return_trace=True):
... super(__class__, self).__init__(return_trace=return_trace)
... self.name = name
...
... def _get_train_tasks(self):
... return lazyllm.pipeline(lambda : print(f'Module {self.name} trained!'))
...
... def _get_deploy_tasks(self):
... return lazyllm.pipeline(lambda : print(f'Module {self.name} deployed!'))
...
... def forward(self, input):
... return f'[Module {self.name} get input: {input}]'
...
>>> m = MyModule('example')
>>> m('hello world')
'[Module example get input: hello world]'
>>> m.update()
Module example trained!
Module example deployed!
<Module type=MyModule name=example>
>>> m.start()
Module example deployed!
<Module type=MyModule name=example>
>>> m.evalset(['hello', 'world'])
>>> m.update().eval_result
Module example trained!
Module example deployed!
['[Module example get input: hello]', '[Module example get input: world]']
Note: The test set is set by calling
evalset
, and there is no need to explicitly override any function. AllModules
can have a test set.
Using the Built-in Registry
LazyLLM implements a registry for Modules
, which allows you to easily register functions as Modules
. Here is a specific example:
>>> import lazyllm
>>> lazyllm.module_register.new_group('mymodules')
>>> @lazyllm.module_register('mymodules')
... def m(input):
... return f'module m get input: {input}'
...
>>> lazyllm.mymodules.m()(1)
'module m get input: 1'
>>> m = lazyllm.mymodules.m()
>>> m.evalset([1, 2, 3])
>>> m.eval().eval_result
['module m get input: 1', 'module m get input: 2', 'module m get input: 3']
Submodules
Concept of Submodules
Similar to the Module
class in pytorch
, the Module
in LazyLLM also has a hierarchical concept, where a Module can have one or more Submodule
.
When using the update
function to update a Module
, its Submodule
will also be updated, unless explicitly set not to update the Submodule
.
Similarly, when using the start
function to start the deployment task of a Module
, its Submodule
will also be deployed, unless explicitly set not to deploy the Submodule
.
Here is an example:
How to Construct Submodules
You can make one Module
a Submodule
of another Module
in the following ways:
-
Pass it as a constructor argument to
ActionModule
orServerModule
, as shown in the example below:>>> m1 = MyModule('m1') >>> m2 = MyModule('m2') >>> am = lazyllm.ActionModule(m1, m2) >>> am.submodules [<Module type=MyModule name=m1>, <Module type=MyModule name=m2>] >>> sm = lazyllm.ServerModule(m1) >>> sm.submodules [<Module type=MyModule name=m1>]
Note:
-
When a flow is passed as a constructor argument to
ActionModule
orServerModule
, anyModule
within it will also become aSubmodule
of theActionModule
orServerModule
. Here's an example:>>> m1 = MyModule('m1') >>> m2 = MyModule('m2') >>> m3 = MyModule('m3') >>> am = lazyllm.ActionModule(lazyllm.pipeline(m1, lazyllm.parallel(m2, m3))) >>> am.submodules [<Module type=MyModule name=m1>, <Module type=MyModule name=m2>, <Module type=MyModule name=m3>] >>> sm = lazyllm.ServerModule(lazyllm.pipeline(m1, lazyllm.parallel(m2, m3))) >>> sm.submodules [<Module type=_ServerModuleImpl>] >>> sm.submodules[0].submodules [<Module type=Action return_trace=False sub-category=Flow type=Pipeline items=[]> └- <Flow type=Pipeline items=[]> |- <Module type=MyModule name=m1> └- <Flow type=Parallel items=[]> |- <Module type=MyModule name=m2> └- <Module type=MyModule name=m3> ]
-
When directly printing the
repr
of aModule
, it will display its hierarchical structure, including all itsSubmodules
. Continuing from the previous example:>>> sm <Module type=Server stream=False return_trace=False> └- <Module type=Action return_trace=False sub-category=Flow type=Pipeline items=[]> └- <Flow type=Pipeline items=[]> |- <Module type=MyModule name=m1> └- <Flow type=Parallel items=[]> |- <Module type=MyModule name=m2> └- <Module type=MyModule name=m3>
-
-
Setting another
Module
as a member variable in aModule
can make the otherModule
become itssubmodule
. Here is an example:>>> class MyModule2(lazyllm.module.ModuleBase): ... ... def __init__(self, name, return_trace=True): ... super(__class__, self).__init__(return_trace=return_trace) ... self.name = name ... self.m1_1 = MyModule('m1-1') ... self.m1_2 = MyModule('m1-2') ... >>> m2 = MyModule2('m2') >>> m2.submodules [<Module type=MyModule name=m1-1>, <Module type=MyModule name=m1-2>]
Utilizing Submodules for Joint Application Deployment
When training/fine-tuning or deploying a Module
, a depth-first strategy will be used to search for all its Submodules
and deploy them one by one. Here is an example:
>>> class MyModule2(lazyllm.module.ModuleBase):
...
... def __init__(self, name, return_trace=True):
... super(__class__, self).__init__(return_trace=return_trace)
... self.name = name
... self.m1_1 = MyModule(f'{name} m1-1')
... self.m1_2 = MyModule(f'{name} m1-2')
...
... def _get_deploy_tasks(self):
... return lazyllm.pipeline(lambda : print(f'Module {self.name} deployed!'))
...
... def __repr__(self):
... return lazyllm.make_repr('Module', self.__class__, subs=[repr(self.m1_1), repr(self.m1_2)])
...
>>> am = lazyllm.ActionModule(MyModule2('m2-1'), MyModule2('m2-2'))
>>> am
<Module type=Action return_trace=False sub-category=Flow type=Pipeline items=[]>
|- <Module type=MyModule2>
| |- <Module type=MyModule name=m2-1 m1-1>
| └- <Module type=MyModule name=m2-1 m1-2>
└- <Module type=MyModule2>
|- <Module type=MyModule name=m2-2 m1-1>
└- <Module type=MyModule name=m2-2 m1-2>
>>> am.update()
Module m2-1 m1-1 trained!
Module m2-1 m1-2 trained!
Module m2-2 m1-1 trained!
Module m2-2 m1-2 trained!
Module m2-1 m1-1 deployed!
Module m2-1 m1-2 deployed!
Module m2-1 deployed!
Module m2-2 m1-1 deployed!
Module m2-2 m1-2 deployed!
Module m2-2 deployed!
Note:
It can be seen that when updating the
ActionModule
, all itsSubmodules
will be updated together. If there are deployment tasks, they will be executed after all the training/fine-tuning tasks are completed. Since parent modules may depend on submodules, submodules will be deployed first, followed by parent modules.Note: When the
Redis
service is configured, the lightweight gateway mechanism provided by LazyLLM can be used to achieve parallel deployment of all services.