Skip to content

Instantly share code, notes, and snippets.

@omegaml
Created January 29, 2020 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save omegaml/9c9811d843fc9245c62e0505d1a6f9d9 to your computer and use it in GitHub Desktop.
Save omegaml/9c9811d843fc9245c62e0505d1a6f9d9 to your computer and use it in GitHub Desktop.
How to develop and deploy omega|ml plugins

How to develop an omega|ml plugin

omega|ml plugins come in two forms:

  • Mixins - extend the functionality of an existing component, e.g. om.datasets, om.modelsor om.runtime
  • Backends - add new capabilities to store and process a certain type of model (framework) or data object

If you want to add support for a new machine learning framework that omega|ml does not support yet, implement a backend. If you want to add pre- and post-processing to some method already provided by omega|ml, implement a mixin.

Implement a Model backend

Model backends have two main responsibilities:

  1. store and retrieve machine learning models - these are the put and get methods
  2. run model actions, e.g. fit, score, predict etc. - these are the fit,score,predictmethods

To implement a backend, write a class as follows:

class MyBackend(BaseModelBackend):
    KIND = 'mybackend.type'
    
	@classmethod
	def supports(cls, obj, name, **kwargs):
		valid = ... # some condition that returns True if the backend supports obj 
		return ok

	def put(self, model, name, **kwargs):
		gridfile = save(model) # your function/code to serialize the model at a persistent location
		meta = self.model_store._make_metadata(  
			   name=name,  
			   prefix=self.model_store.prefix,  
			   bucket=self.model_store.bucket,  
			   kind=MyBackend.KIND,
			   attributes=attributes,  
			   gridfile=gridfile).save()
	   return meta

	def get(self, name, **kwargs):
		model = load(...) # your load function/code to deserialize the model
		return model

	def fit(self, modelname, Xname, Yname, **kwargs):
		X = om.data_store.get(Xname)
		Y = om.data_store.get(Yname)
		model = om.model_store.get(model)
		model = om.model_store.fit(X, Y)
		meta = om.model_store.put(model, modelname)
		return meta

Notes:

  • the supports method is a @classmethod for efficiency - omega|ml will only instantiate the class if there is some work to perform.
  • model backends do not need to know where and how the data is stored, but they need to know how to get data from omega|ml. For this, the model backend provides the .data_storewhich is the same as om.datasets
  • it is the backend's own responsibility to ensure the data retrieved matches the type and structure the model requires. The good news is that by omega|ml's name-based object resolution any data type and source can be supported
  • at runtime, the backend will be instantiated in both the client (e.g. in Jupyter Notebook) as well as omega|ml's runtime environment in the cloud. Therefore the backend will have to be deployed to both your users and your cloud instance of omega|ml.

Testing a model backed

It is important to test your backend before you can run it. Here's how, in a nutshell

import omegaml as om
backend = MyBackend(data_store=om.datasets, model_store=om.models)
backend.put(model, 'name') 
model_ = backend.get('name')
assert type(model_) == type(model)
model.fit(...)
model.predict(...)

Packaging and deployment

To deploy your model onto an omega|ml cluster and make it ready for use by your users and applications, you need to package it. Packaging follows the same process as for any Python package, namely by writing a pip-installable module. There is a nice tutorial at https://packaging.python.org/tutorials/packaging-projects/

The gist of it is this:

  • create a pip-installable package that contains your backend code
  • install the backend so that all omega|ml components have access to it
  • register the backend to omega|ml

Here's the gory details:

  1. create a Python module

     $ mkdir mybackend
     $ touch mybackend/__init__.py
     
     # add your backend's code in mybackend.__init__.py
     class MyBackend:
     	....
    
  2. write a setup.py

     from setuptools import setup
     setup(name='funniest',
           version='0.1',
           description='mybackend for omega/Uml',
           url='http://github.com/foobar/mybackend',
           author='Flying Circus',
           author_email='flyingcircus@example.com',
           license='MIT',
           packages=['mybackend'],
           zip_safe=False)
    
  3. check everything into github, so it is accessible for installation from the cloud (if you run omega|ml locally you can skip this step)

  4. Write the omega|ml configuration file, config.yml

     OMEGA_USER_EXTENSIONS:  
        OMEGA_STORE_BACKENDS:  
             mybackend.type: mybackend.MyBackend
    
  5. Map your config.yml into docker-compose

     # find all the volumes: statements in docker-compose.yml, amend like this
     services:
     	...
     	volumes:
     		- pythonlib:/app/pylib
     		- /host/path/to/config.yml:/app/config.yml
    
     volumes:
     	pythonlib: 
    
  6. Install your package created above

     # install in pylib
     $ mkdir /path/to/pylib
     $ pip install --target /path/to/pylib
    
  7. Restart omega|ml

     $ docker-compose restart
    
  8. Test that it works

     # open your jupyter notebook and work with your plugin
     # say your plugin can work with some new model types
     # -- save the model
     model = SomeNewModel()
     meta = om.models.put(model, 'name')
     assert meta is not None
     # -- load the model
     model_ = om.models.get('name')
     assert type(model_) == type(model)
     # -- run fit using the runtime
     meta_ = om.runtime.model('name').fit(X, y).get()
     assert 'Metadata' in meta_
    

Written with StackEdit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment