diff --git a/genai-cookbook/.dockerignore b/genai-cookbook/.dockerignore new file mode 100644 index 0000000..3b2ce3d --- /dev/null +++ b/genai-cookbook/.dockerignore @@ -0,0 +1,11 @@ +node_modules +.next +.git +.env*.local +*.log +npm-debug.log* +.DS_Store +.vscode +.idea +coverage +.cache diff --git a/genai-cookbook/Dockerfile b/genai-cookbook/Dockerfile new file mode 100644 index 0000000..d79e8c5 --- /dev/null +++ b/genai-cookbook/Dockerfile @@ -0,0 +1,29 @@ +ARG GPU_TYPE=nvidia +FROM modular/max-${GPU_TYPE}-base + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + curl \ + build-essential \ + git \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +RUN npm install -g pm2 wait-on@7.2.0 + +COPY package*.json ./ + +RUN npm install + +COPY . /app + +RUN npm run build + +ENTRYPOINT [] + +CMD ["pm2-runtime", "start", "ecosystem.config.js"] diff --git a/genai-cookbook/README.md b/genai-cookbook/README.md index 62c7f0c..97b3368 100644 --- a/genai-cookbook/README.md +++ b/genai-cookbook/README.md @@ -100,53 +100,31 @@ genai-cookbook/ │ ├── api/ # API routes │ │ ├── endpoints/ # Endpoint API handler │ │ └── models/ # Models API handler +│ │ │ ├── cookbook/ # Cookbook pages │ │ ├── [recipe]/ # Dynamic recipe routes -│ │ │ ├── page.tsx # Recipe page -│ │ │ ├── code/ # Code view page +│ │ │ ├── page.tsx # Recipe UI (with lazy-load) +│ │ │ ├── code/ # Recipe code viewer │ │ │ └── api/ # Recipe API handler +│ │ │ │ │ ├── page.tsx # Cookbook home │ │ └── layout.tsx # Cookbook layout +│ │ │ ├── page.tsx # Landing page │ └── layout.tsx # Root layout +│ ├── recipes/ # Recipe implementations │ ├── multiturn-chat/ │ │ ├── ui.tsx # Frontend UI component │ │ ├── api.ts # Backend API logic │ │ └── recipe.json # Recipe metadata │ └── image-captioning/ -│ ├── ui.tsx -│ ├── api.ts -│ └── recipe.json +│ ├── components/ # Reusable UI components -│ ├── Header.tsx # App header -│ ├── Navbar.tsx # Navigation sidebar -│ ├── Toolbar.tsx # Recipe toolbar -│ ├── CodeToggle.tsx # Code view toggle -│ ├── ThemeToggle.tsx # Theme switcher -│ ├── SelectEndpoint.tsx # Endpoint selector -│ ├── SelectModel.tsx # Model selector -│ └── BodyText.tsx # Text component -├── hooks/ # Custom React hooks -│ ├── useCookbook.ts # Endpoint/model selection -│ ├── CookbookProvider.tsx # Context provider -│ └── ClientThemeProvider.tsx # Theme provider -├── lib/ # Utility functions -│ ├── constants.ts # Shared constants -│ ├── types.ts # TypeScript types -│ └── prepareModel.ts # AI SDK model preparation -├── store/ # State management -│ ├── EndpointStore.ts # Endpoint configuration -│ └── RecipeStore.ts # Recipe state +├── hooks/ # Endpoint/model/theme selection +├── lib/ # Types and misc. utilities +├── store/ # In-memory store for configuration └── theme/ # Theming and styles - ├── partials/ # SCSS partials - │ ├── _base.scss - │ ├── _colors.scss - │ ├── _font.scss - │ └── _mantine.scss - ├── globals.scss # Global styles - ├── theme.ts # Theme configuration - └── tailwindTheme.js # Tailwind config ``` ## Development @@ -176,7 +154,7 @@ To use the cookbook with MAX: 1. **Start the model server** (in a separate terminal): ```bash - max serve --model meta-llama/Llama-3.1-8B-Instruct + max serve --model google/gemma-3-27b-it ``` For more details, see the [MAX quickstart](https://docs.modular.com/max/get-started/). @@ -195,6 +173,67 @@ To use the cookbook with MAX: 3. **Select MAX** in the cookbook UI endpoint selector +## Running with Docker + +The GenAI Cookbook can be run entirely within a Docker container, including the MAX model server and web application. + +### Building the Container + +The Dockerfile defaults to NVIDIA GPUs: + +```bash +docker build --ulimit nofile=65535:65535 -t max-recipes:latest . +``` + +Use the `--build-arg` flag to specify AMD: + +```bash +docker build --build-arg GPU_TYPE=amd --ulimit nofile=65535:65535 -t max-recipes:latest . +``` + +**Note:** The `--ulimit nofile=65535:65535` flag increases the file descriptor limit, which is needed for Next.js builds. + +### Running the Container + +#### With NVIDIA GPUs + +```bash +docker run --gpus all \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + --env "HF_HUB_ENABLE_HF_TRANSFER=1" \ + --env "HF_TOKEN=your-huggingface-token" \ + --env "MAX_MODEL=google/gemma-3-27b-it" \ + -p 8000:8000 \ + -p 3000:3000 \ + max-recipes:latest +``` + +#### With AMD GPUs + +```bash +docker run \ + --group-add keep-groups \ + --rm \ + --device /dev/kfd \ + --device /dev/dri \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + --env "HF_HUB_ENABLE_HF_TRANSFER=1" \ + --env "HF_TOKEN=your-huggingface-token" \ + --env "MAX_MODEL=google/gemma-3-27b-it" \ + -p 8000:8000 \ + -p 3000:3000 \ + max-recipes:latest +``` + +**Configuration:** +- **Port 8000**: MAX model serving endpoint +- **Port 3000**: GenAI Cookbook web application +- **HF_TOKEN**: Your HuggingFace token for downloading models +- **MAX_MODEL**: The model to serve (e.g., `google/gemma-3-27b-it`) +- **Volume mount**: Caches downloaded models in `~/.cache/huggingface` + +Once running, navigate to [http://localhost:3000](http://localhost:3000) to access the cookbook. + ## Available Scripts - `npm run dev` - Start development server with hot reloading diff --git a/genai-cookbook/ecosystem.config.js b/genai-cookbook/ecosystem.config.js new file mode 100644 index 0000000..da255dd --- /dev/null +++ b/genai-cookbook/ecosystem.config.js @@ -0,0 +1,41 @@ +module.exports = { + apps: [ + { + name: 'max-llm', + script: 'max', + args: `serve --model ${process.env.MAX_MODEL || 'google/gemma-3-27b-it'} --trust-remote-code`, + interpreter: 'none', + autorestart: true, + watch: false, + max_memory_restart: '4G', + env: { + MODULAR_STRUCTURED_LOGGING: 'False', + MAX_SERVE_PORT: 8000, + NODE_ENV: 'production', + }, + }, + { + name: 'web-app', + script: '/bin/bash', + args: [ + '-c', + 'wait-on http-get://0.0.0.0:8000/health -t 600000 -i 2000 && npm start', + ], + interpreter: 'none', + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'production', + PORT: 3000, + COOKBOOK_ENDPOINTS: JSON.stringify([ + { + id: 'max', + baseUrl: 'http://0.0.0.0:8000/v1', + apiKey: 'EMPTY', + }, + ]), + }, + }, + ], +} diff --git a/genai-cookbook/store/EndpointStore.ts b/genai-cookbook/store/EndpointStore.ts index e7becc8..27873d1 100644 --- a/genai-cookbook/store/EndpointStore.ts +++ b/genai-cookbook/store/EndpointStore.ts @@ -9,23 +9,23 @@ type ServerSideEndpoints = EndpointWithApiKey[] | null // Store endpoints server-side in memory to access them across routes class EndpointStore { - private _endpoints: ServerSideEndpoints = null + private endpoints: ServerSideEndpoints = null getAll(): ServerSideEndpoints { - return this._endpoints + return this.endpoints } set(newEndpoints: ServerSideEndpoints) { - this._endpoints = newEndpoints + this.endpoints = newEndpoints } apiKey(endpointId: string | null | undefined): string | undefined { - const endpoint = this._endpoints?.find((e) => e.id === endpointId) + const endpoint = this.endpoints?.find((e) => e.id === endpointId) return endpoint?.apiKey } baseUrl(endpointId: string | null | undefined): string | undefined { - const endpoint = this._endpoints?.find((e) => e.id === endpointId) + const endpoint = this.endpoints?.find((e) => e.id === endpointId) return endpoint?.baseUrl } }