Skip to content

[RFC] Services are all you need? #133

@Jack-Khuu

Description

@Jack-Khuu

(Based on conversations with @pbontrager and @DNXie)

Currently, Forge users are exposed to 2 concepts: Services (spawns processes and replicas) and ForgeActor (class doing the "work"). Should we flatten this mental model so that users only think of everything as a Service instead?

From a user's perspective, Services are a wrapper around Actor instances, with the routing setup logic intentionally abstracted away from the user. The API for spawning a Service even literally takes the underlying Actor as an argument.

https://github.com/meta-pytorch/forge/blob/4372a5490d748889070fa6374499a274185eb540/src/forge/controller/service/spawn.py#L22-L24


Option A: Just a rebrand

async def spawn_service( 
     service_cfg: ServiceConfig, actor_def: Type[Service], **actor_kwargs 
 ) -> ServiceInterface: 

# Where ServiceInterface is mentally mapped as the functionality of both the Router and Service

Option B: Flatten the layers

Currently, the architecture is layered such that each class has a distinct responsibility:

  • ServiceInterface wraps Service and exposes select meta APIs from it's nested child classes (actor, replica, etc)
  • Service has a List[Replica] and deals with routing and load balancing
    • Recall that the final Service implementation should be an Actor itself
  • Replica has ForgeActor and manages the request queueing
  • ForgeActor has the actual endpoints that users write and adds Logging
  • Actor is your base Monarch Actor

This looks great from a "Single Responsibility Design" aspect, but should we flatten the overall architecture to reduce this depth? Effectively moving from "instantiate a new class and adding it as a class member" to "populating a field"

Specifically, combining the interfaces of Service and ForgeAgent, call it SuperService for the sake of discussion

class SuperService(Actor):
    # All the interfaces in ForgeActor
    # These + @endpoints are the things only things users implement
    setup()
    launch()
    shutdown()

    # From Service
    call()/call_all()
    start()/stop_session()
    replica: List[Replica] # This one is a little tricky since it wraps an actor

class MyService(SuperService):
    @endpoint
    async def foo(self):
        print("a")

I haven't fully fleshed out the design/feasibility (@DNXie you should talk to @allenwang28 to flesh this out if we decide to do this)


Thoughts on the idea of making the idea of Service the user mental model, regardless of approach?

Are the layers of Option B unavoidable?

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions