Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions genai-cookbook/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.next
.git
.env*.local
*.log
npm-debug.log*
.DS_Store
.vscode
.idea
coverage
.cache
29 changes: 29 additions & 0 deletions genai-cookbook/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 [email protected]

COPY package*.json ./

RUN npm install

COPY . /app

RUN npm run build

ENTRYPOINT []

CMD ["pm2-runtime", "start", "ecosystem.config.js"]
105 changes: 72 additions & 33 deletions genai-cookbook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/).
Expand All @@ -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
Expand Down
41 changes: 41 additions & 0 deletions genai-cookbook/ecosystem.config.js
Original file line number Diff line number Diff line change
@@ -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',
},
]),
},
},
],
}
10 changes: 5 additions & 5 deletions genai-cookbook/store/EndpointStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down