Computer VisionProgrammingCloud Development

Perfect Form: An Independent Entity

Navigating the legal process for accepting users.

10 min read

Three years ago, Perfect Form was a simple prototype running on my local computer for powerlifting form analysis created with the specific intent of learning how to host and deploy deep learning solutions. Since that early experimentation, it has been shipped to run serverlessly on the cloud, the models have been upgraded to the latest and greatest, it was partially moved off the cloud to reduce serving latency, and it received a bespoke harness to quickly and reliably handle occlusions. While the website and application have been live for more than a year, so far only my close friends have been able to use the platform under a closed beta release. Without an access code, interested users have been left staring at a grayed out Stripe link and forced to join a waitlist rather than actually try the tool. At this point, I’m in too deep to draw the finish line here.

Previously, the dominant reason why Perfect Form was not generally available was simply that the performance was too low to charge anyone to use. However, switching to SAM3 and adding the harness removed this specific concern, leaving two remaining reasons to gate access. First, the economics of a product that requires always-on GPUs to run inference on compute-heavy computer vision models and frontier lab LLMs to create digestible interpretations of the biomechanical metrics do not would with a free model. Second, I was not ready to handle the data retention policies, terms of service, or associated liability required to safely allow people outside of my friend group to use the website.

Creating an LLC would provide indirect solutions to both of these concerns. I could start to process Stripe payments after I had an LLC and a dedicated EIN, which would offset the cost of service concerns with more users. And more importantly, an LLC would provide a legal barrier separating my personal assets from those of the business. I would still need to work with a lawyer to draft the proper terms of service and liability waiver and make sure that I was in compliance with all of the applicable laws, but the LLC would provide the protection required to launch the site without incurring significant risk.

To be very clear, the real benefit of an LLC would be that I could broadly share the platform that I had built with a much higher floor on the range of possible outcomes. In my mind, the ceiling is quite low. While the hosting costs for the application are not particularly high, acquiring enough users to generate the revenue needed to offset the new collection of legal costs seems quite challenging (more on this later), especially without proper marketing. I am not under any delusion that this will prove to be a significant income stream. Somehow, a few dozen users have stumbled onto the site and were curious enough to join the waitlist without any ads or posts about its existence (minus this blog, obviously). If nothing else, I want them to get to experience the software that I spent so many late nights building.

This part was actually rather straightforward. Chatting with a lawyer confirmed my belief that an LLC was the appropriate avenue towards incorporation. He provided me with an operating agreement for Perfect Form, and I filed the articles of organization with the secretary of state. I opted to go through a commercial agent for the LLC filing, which would buy privacy for an annual fee. Then, I obtained an EIN from the IRS and opened a bank account in the name of Perfect Form. There are a number of online-only banks focused on small businesses, and I was able to get an account created and funded with little friction. If I were creating a more typical SaaS (and had started out the right way), I would be finished.

Insurance

This was one of the big surprises to me. My lawyer strongly suggested that I purchase insurance before I open up Perfect Form to users. There are two concerns specific to this class of software. First, there are the generic concerns around data breaches but elevated since user videos could constitute sensitive data. Second, there is a need for bodily injury coverage broadly associated with providing advice to people engaging in potentially dangerous exercises. Unfortunately, these greatly increase the monthly prorated business expenses by several multiples, which made this a much more expensive endeavor than I anticipated. However, I could not reasonably release the application without these safeguards in place, so I did not feel like I had much choice here.

Without going on too much of a rant here, I don’t understand how my hobby project site has a better implementation of unindexed shared links than Anthropic. Yet despite taking the time to put basic thought into the privacy of my users, I need to pay a small fortune to be safe against possible lawsuits. Certainly, I have been the victim of countless significantly more serious breaches of my personal information, but I have never received any meaningful compensation when other companies fail to adopt a basic security posture. If these data breach lawsuits don’t lead to real payouts, why is the insurance that I need to purchase so expensive yet still necessary when cases of actual negligence are barely punished? There seems to be a stark asymmetry here heavily in favor of corporations that I have experienced many times as a consumer that somehow then flips when I find myself on the other side as a small business owner.

Shielding

Remember when I mentioned how the LLC would serve as a legal barrier between myself and the business? Well, that’s only true if all of my personal accounts are cleanly separated from those of the business. While many of these accounts were quite easy to change, I made the major mistep of setting up all of the Perfect Form infrastructure in my personal AWS account. This turned out to be a costly error. I never transitioned my application code to IaC, so everything generated via clickops had to be regenerated as clickops. If I simply wanted to recreate my existing front end and back end infrastructure in a new account, this would be the perfect opportunity for me to first migrate everything into Terraform and then test the migration by trying to set up everything from scratch in the dedicated AWS account. However, I took a different route. There are actually several properties of my existing implementation that I had wanted to change for a while but never had a good enough reason to invest the time.

Architecture Overhaul

There were a series of assorted housekeeping items to clean up while I was rolling up my sleeves, but there were other decisions that more fundamentally changed the application. Principally, there were two decisions that were reasonable at the time they were made but that I now wanted to undo:

  1. Architecting the backend as an event-driven serverless application.
  2. Relying on cog to serve the computer vision models.

If we had to uproot our implementation anyway, we ought to take the time to leave the system design in a better place than where we had begun.

So Long, Events (Mostly)

Initially, when this was a simple hobby project, I opted for an event-driven architecture knowing that it was overly complex for my use case but wanted an excuse to try desiging such a system anyway. Well I certainly learned a lot, it was time to redesign the backend to be less scalable but with the added benefit of greatly reducing the complexity. We already traded away scalability when we shifted to our hybrid cloud + on-prem hosting, so we were not really sacrificing anything (and obviously we were never in the usage regime where scalability was a legitimate concern). Most of the event-driven design patterns were replaced with something resembling a lambdalith, although we still kept the existing lambda functions and instead called them directly as needed rather than relied on event triggers.

We actually did keep the event-driven architecture in two places where it still made sense. First, the initial upload of a raw video into the appropriate S3 bucket was still used to trigger all of the metadata extraction and video preprocessing required before the models could perform inference. This balance provided easier testing of individual components in our pipeline while still decoupling the front end from the back end. Second, we relied on an event-driven trigger once the on-prem computer had finished extracting the poses from the video, which would kick off a series of data transforms and post-processing to get the data in the necessary shape for the front end. This allowed the system to degrade gracefully if it did become backed up by a long queue of inference jobs.

ML Orchestration

Speaking of inference jobs, the other major change came from how we configured the on-prem model serving. In a previous iteration of this project, we relied on Replicate for running serverless GPU inference, which required that models be packaged and served using cog. I have already complained about cog in an earlier post, but briefly it is basically a hamstrung version of Docker with some minor quality of life improvements around managing endpoints. One of the several pain points that I encountered is that cog cannot handle concurrent requests. Actually, looking through the documentation today this no longer seems to be the case, but it was an issue with the earlier releases. Requiring the lambda function calling into my homelab through a CloudFlare tunnel to manage a semaphore was a major design smell. Also, this is a surprisingly subtle resource to manage in a lambda function, especially when you start thinking about whether an additional request comes from a separate lambda function or the first function that finishes but is still warm when the next request comes in.

Another component of technical debt that reflected the earlier cog pattern was that we ended up calling to three separate endpoints hosted on the homelab, matching what was previously required for Replicate inference. As a result, we were tasking the cloud lambda function with orchestrating the on-prem models. A much cleaner solution to both problems naturally emerged. The lambda function could call a single orchestration endpoint, exposed from the homelab via a dedicated Cloudflare tunnel, and it was the responsibility of the orchestrator to manage the various inference jobs. We could then define several internal endpoints, set up as microservices using fastAPI and managed by Docker Compose, that could be used by the master orchestrator. The orchestrator would then be responsible for managing its own queue of work items, since our limited GPU memory prevents us from running multiple copies of any single model.

One added benefit of this approach is greatly reduced bandwidth. Previously, the lambda function coordinating the models would have to send very large payloads through the Cloudflare for each individual model, since each model required the input video as well as the output from any upstream models. Now, the lambda function could simply send the new orchestrator service a signed S3 URL of the video to be analyzed as well as a second signed URL for the final inference artifact produced when all of the models had completed. This allowed the on-prem system to be completely decoupled from the cloud infrastructure, and no AWS permissions were required to process videos since all communication occurred over these S3 signed URLs.

What’s Next

If you visit Perfect Form at the time this article is published, you might be disappointed to see that the site is still in closed beta and the Stripe link is still grayed out. While most of the major blockers have been removed, there are still a few oustanding items to complete before I can accept new users, mostly legal items like the disclaimer, terms of service, etc. that are still being reviewed. Once we get all of the wording locked down, I will be excited to onboard new users and let everyone try out the product. The long road it has taken to reach this point will only make the official release all the more exciting.