<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Pasha Shaik on Medium]]></title>
        <description><![CDATA[Stories by Pasha Shaik on Medium]]></description>
        <link>https://medium.com/@pashashaik?source=rss-492b6b477184------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*E7l0AYUX1mcnD1GDZDWDFg.png</url>
            <title>Stories by Pasha Shaik on Medium</title>
            <link>https://medium.com/@pashashaik?source=rss-492b6b477184------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 17:33:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@pashashaik/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How We Reduced Our Next.js Docker Image from 2.1GB to 180MB]]></title>
            <link>https://medium.com/@pashashaik/how-we-reduced-our-next-js-docker-image-from-2-1gb-to-180mb-35fa8ea4160d?source=rss-492b6b477184------2</link>
            <guid isPermaLink="false">https://medium.com/p/35fa8ea4160d</guid>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Pasha Shaik]]></dc:creator>
            <pubDate>Sat, 20 Dec 2025 22:22:28 GMT</pubDate>
            <atom:updated>2025-12-20T22:22:28.005Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l2CXFnNUX-tLPHP6P5GuNw.png" /></figure><p><strong>TL;DR</strong></p><p>We reduced our Next.js Docker image by 91% (2.1GB to 180MB) using multi-stage builds, Alpine base, standalone mode, and layer caching. Results: 8x faster deploys, 80%+ lower registry costs, pod startup dropped from 90s to 12s.</p><h3>How We Reduced Our Next.js Docker Image from 2.1GB to 180MB</h3><p>We deploy our Next.js frontend to Kubernetes multiple times per day. A few months ago, each deploy was painful. Our Docker image had ballooned to 2.1GB. Pull times exceeded 6 minutes. Registry costs were climbing. Security scans flagged 47 vulnerabilities from packages we didn’t even need.</p><p>We got it down to 180MB. Here’s the complete step-by-step journey.</p><p>This article originally appeared on the <strong>Zingaro AI Engineering Blog</strong>. <a href="https://zingaro.ai/engineering-blog/docker-image-optimization">Click here</a> to see how we are building the future of human-like voice agents.</p><h3>How We Reduced Our Next.js Docker Image from 2.1GB to 180MB</h3><p>We deploy our Next.js frontend to Kubernetes multiple times per day. A few months ago, each deploy was painful. Our Docker image had ballooned to 2.1GB. Pull times exceeded 6 minutes. Registry costs were climbing. Security scans flagged 47 vulnerabilities from packages we didn’t even need.</p><p>We got it down to 180MB. Here’s the complete step-by-step journey.</p><h3>The Starting Point: What Went Wrong</h3><p>Our original Dockerfile looked like every tutorial on the internet:</p><pre>FROM node:20<br>WORKDIR /app<br>COPY . .<br>RUN npm install<br>RUN npm run build<br>EXPOSE 3000<br>CMD [&quot;npm&quot;, &quot;start&quot;]</pre><p>Simple. Elegant. And absolutely terrible for production.</p><p>This creates a 2GB+ disaster:</p><p>ProblemImpactFull Node.js Debian image~1GB base before your codeCOPY . . before installBusts cache on every file changenpm install includes devDepsHundreds of MB of build toolsNo .dockerignoreCopies .git, node_modules, testsBuild artifacts left in image.next/cache, source mapsRoot userSecurity vulnerability</p><p><strong>Result: 2.1GB image, 47 security vulnerabilities, 6+ minute pulls.</strong></p><p>We couldn’t keep shipping like this.</p><h3>Step 1: Switch to Alpine Base Image</h3><p>The single biggest win. One line change that cuts 860MB.</p><p>Alpine Linux is a security-focused, lightweight distribution. The Node.js Alpine image is ~140MB vs ~1GB for Debian-based images.</p><pre># Before: ~1GB base<br>FROM node:20# After: ~140MB base<br>FROM node:24-alpine</pre><p>Alpine uses musl instead of glibc, which can cause issues with some native modules. Add compatibility layer to be safe:</p><pre>FROM node:24-alpine<br>RUN apk add --no-cache libc6-compat</pre><p><strong>Impact: -860MB immediately.</strong></p><h3>Step 2: Multi-Stage Builds</h3><p>This is the architecture that makes everything else possible. We split the build into three distinct stages:</p><pre>+------------------+     +------------------+     +------------------+<br>|     Stage 1      |     |     Stage 2      |     |     Stage 3      |<br>|      deps        | --&gt; |     builder      | --&gt; |     runner       |<br>|                  |     |                  |     |                  |<br>|  Install prod    |     |  Install all     |     |  Copy only       |<br>|  dependencies    |     |  deps + build    |     |  what&#39;s needed   |<br>+------------------+     +------------------+     +------------------+<br>      ~200MB                   ~900MB                   ~180MB</pre><p>Each stage starts fresh. Only the final stage goes to production. All the build cruft stays behind.</p><h3>Stage 1: Dependencies</h3><pre>FROM node:24-alpine AS deps<br>RUN apk add --no-cache libc6-compat<br>RUN corepack enable &amp;&amp; corepack prepare pnpm@10.22.0 --activate<br>WORKDIR /app<br>COPY package.json pnpm-lock.yaml ./<br>RUN pnpm install --prod --frozen-lockfile</pre><p><strong>Key points:</strong></p><ul><li>--prod flag: Only production dependencies</li><li>--frozen-lockfile: Fails if lockfile is outdated (reproducible builds)</li><li>Copy only package.json and lockfile first for layer caching</li></ul><h3>Stage 2: Builder</h3><pre>FROM node:24-alpine AS builder<br>RUN corepack enable &amp;&amp; corepack prepare pnpm@10.22.0 --activate<br>WORKDIR /app<br>COPY --from=deps /app/node_modules ./node_modules<br>COPY . .<br>COPY .env.build ./.env<br>RUN pnpm install --frozen-lockfile<br>ENV NODE_ENV=production<br>RUN pnpm build</pre><p>This stage:</p><ul><li>Copies prod deps from stage 1 (layer cache hit)</li><li>Installs ALL deps (including dev) for the build</li><li>Runs the actual Next.js build</li><li>Everything here stays in this stage</li></ul><h3>Stage 3: Runner (Production)</h3><pre>FROM node:24-alpine AS runner<br>WORKDIR /app<br>RUN addgroup --system --gid 1001 nodejs &amp;&amp; \<br>    adduser --system --uid 1001 nextjs<br>ENV NODE_ENV=production<br>ENV PORT=3000<br>COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./<br>COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static<br>COPY --from=builder --chown=nextjs:nodejs /app/public ./public<br>USER nextjs<br>EXPOSE 3000<br>CMD [&quot;node&quot;, &quot;server.js&quot;]</pre><p>This stage starts completely fresh:</p><ul><li>New Alpine image (no build cruft)</li><li>Non-root user for security</li><li>Only three directories copied from builder</li></ul><h3>Step 3: Enable Next.js Standalone Mode</h3><p>This is the secret weapon. In next.config.js:</p><pre>module.exports = {<br>  output: &#39;standalone&#39;,<br>}</pre><p>Next.js traces your actual imports and bundles only what’s needed into a self-contained server:</p><pre>.next/standalone/<br>├── server.js           # Self-contained server<br>├── node_modules/       # Only traced dependencies (~30MB)<br>└── [your app code]</pre><p><strong>Before standalone:</strong> Copy 500MB+ of node_modules <strong>After standalone:</strong> Copy ~30MB of traced dependencies</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/669/1*zYrO4W2iE5amE6ir7h0_Bg.png" /></figure><p><strong>No full node_modules. No source files. No devDependencies.</strong></p><h3>Step 4: Optimize Layer Caching</h3><p>Docker caches layers. If a layer hasn’t changed, Docker reuses it. Order matters.</p><pre># BAD: Any file change busts the cache<br>COPY . .<br>RUN npm install<br># GOOD: Dependencies cached unless package.json changes<br>COPY package.json pnpm-lock.yaml ./<br>RUN pnpm install<br>COPY . .</pre><p>Dependencies change rarely. Source code changes constantly. Put stable things first.</p><p><strong>Build time impact:</strong></p><ul><li>First build: ~4 minutes</li><li>Subsequent builds (no dep changes): ~45 seconds</li></ul><h3>Step 5: Configure .dockerignore</h3><p>Don’t copy garbage into the build context:</p><pre># Version control<br>.git<br>.gitignore<br># Dependencies (each stage installs fresh)<br>node_modules<br># Build outputs (we build fresh)<br>.next<br>out<br>build<br># Environment files (secrets!)<br>.env<br>.env.local<br>.env.development<br>.env.production<br>.env.prod<br># Allow only build-time env vars<br>!.env.build<br># Development files<br>.vscode<br>.idea<br>*.log<br>*.md<br>tests<br>__tests__<br>coverage<br># Docker files<br>Dockerfile<br>docker-compose.yml</pre><p><strong>Impact: Build context reduced from 1.2GB to 180MB.</strong></p><h3>Step 6: Separate Build-Time vs Runtime Environment Variables</h3><p>This trips up many teams. Next.js has two types of env vars:</p><p>TypeWhen EmbeddedStorageNEXT_PUBLIC_*Build time (baked into JS).env.build in DockerEverything elseRuntimeContainer orchestrator secrets</p><p><strong>Wrong approach (security risk):</strong></p><pre>COPY .env.prod ./.env  # Secrets baked into your image!</pre><p><strong>Correct approach:</strong></p><pre># Build stage - only public vars<br>COPY .env.build ./.env</pre><p><strong>.env.build (safe to embed):</strong></p><pre>NEXT_PUBLIC_APP_NAME=MyApp<br>NEXT_PUBLIC_API_URL=https://api.example.com</pre><p><strong>.env.prod (never in Docker):</strong></p><pre>DATABASE_URL=postgres://...<br>API_SECRET_KEY=sk_...<br>PAYMENT_API_KEY=...</pre><p>Runtime secrets injected via orchestrator:</p><pre># Kubernetes example<br>envFrom:<br>  - secretRef:<br>      name: app-secrets</pre><h3>Step 7: Use pnpm Instead of npm/yarn</h3><p>pnpm is faster and more disk-efficient:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/671/1*Qs-9G6i0SLo-G_235DQJiQ.png" /></figure><p>Enable via Corepack (built into Node 16+):</p><pre>RUN corepack enable &amp;&amp; corepack prepare pnpm@10.22.0 --activate</pre><h3>Step 8: Run as Non-Root User</h3><p>Security best practice. If your container is compromised, the attacker has limited permissions:</p><pre>RUN addgroup --system --gid 1001 nodejs &amp;&amp; \<br>    adduser --system --uid 1001 nextjs<br># Copy files with proper ownership<br>COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./<br>USER nextjs</pre><h3>Step 9: Server External Packages</h3><p>Some packages are only used on the server (API routes, server components) but get bundled anyway. Tell Next.js to exclude them:</p><pre>// next.config.js<br>module.exports = {<br>  output: &#39;standalone&#39;,<br>  serverExternalPackages: [<br>    &#39;firebase-admin&#39;,  // ~20MB, server-only SDK<br>    &#39;nodemailer&#39;,      // Email sending<br>    &#39;pg&#39;,              // PostgreSQL client<br>  ],<br>}</pre><p>These packages use native Node.js require() instead of being webpack-bundled. Saves 10-30MB depending on your stack.</p><h3>Step 10: Optimize Package Imports (Tree Shaking)</h3><p>Many popular packages use “barrel exports” — one index file that re-exports everything. Without optimization, importing one icon imports the entire library.</p><pre>// next.config.js<br>module.exports = {<br>  experimental: {<br>    optimizePackageImports: [<br>      &#39;lucide-react&#39;,     // Icon library<br>      &#39;date-fns&#39;,         // Date utilities<br>      &#39;lodash&#39;,           // Utility functions<br>      &#39;framer-motion&#39;,    // Animations<br>      &#39;recharts&#39;,         // Charts<br>    ],<br>  },<br>}</pre><p>Next.js transforms barrel imports into direct imports, enabling proper tree-shaking.</p><p><strong>Before:</strong> import { format } from &#39;date-fns&#39; pulls in entire library <strong>After:</strong> Only the format function is bundled</p><h3>Step 11: Audit and Remove Unused Packages</h3><p>Dead dependencies are dead weight. Find them:</p><pre># Check what&#39;s actually imported<br>npx depcheck<br># Or manually grep for imports<br>grep -r &quot;from &#39;package-name&#39;&quot; src/</pre><p>We found several packages installed but never used. Removing them saved ~15MB from node_modules before the build even starts.</p><h3>Step 12: node-prune (Remove Junk from node_modules)</h3><p>Even after standalone tracing, node_modules contains junk: README files, changelogs, TypeScript definitions, test files, source maps.</p><p>Add node-prune to the builder stage:</p><pre># After pnpm build<br>RUN apk add --no-cache curl &amp;&amp; \<br>    curl -sf https://gobinaries.com/tj/node-prune | sh &amp;&amp; \<br>    node-prune .next/standalone/node_modules</pre><p>node-prune removes:</p><ul><li>*.md files</li><li>*.map source maps</li><li>__tests__ directories</li><li>*.d.ts type definitions</li><li>Documentation folders</li></ul><p><strong>Impact: 10–20MB savings.</strong></p><h3>Step 13: Distroless Base Image (Advanced)</h3><p>For maximum security and minimum size, replace Alpine with Google’s Distroless:</p><pre># Instead of Alpine (~50MB)<br>FROM node:24-alpine AS runner<br># Use Distroless (~30MB)<br>FROM gcr.io/distroless/nodejs22-debian12 AS runner</pre><p>Distroless contains <strong>only</strong> the Node.js runtime. No shell, no package manager, no utilities.</p><pre>FROM gcr.io/distroless/nodejs22-debian12 AS runner<br>WORKDIR /app<br>ENV NODE_ENV=production<br>ENV PORT=3000<br># Distroless runs as nonroot (uid 65532) by default<br>COPY --from=builder --chown=65532:65532 /app/.next/standalone ./<br>COPY --from=builder --chown=65532:65532 /app/.next/static ./.next/static<br>COPY --from=builder --chown=65532:65532 /app/public ./public<br>USER nonroot<br>EXPOSE 3000<br>CMD [&quot;server.js&quot;]</pre><p><strong>Trade-off:</strong> You can’t kubectl exec into the container for debugging. Rely on logs instead.</p><p><strong>Impact: 20MB smaller, near-zero attack surface.</strong></p><h3>Step 14: Optimize Static Assets</h3><p>Your public/ folder adds directly to image size. Optimize images:</p><pre># Convert PNGs to WebP (70-90% smaller)<br>for f in public/*.png; do<br>  cwebp -q 85 &quot;$f&quot; -o &quot;${f%.png}.webp&quot;<br>  rm &quot;$f&quot;<br>done</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/661/1*GkX_q0pPZU6LhffyLonAPg.png" /></figure><p>Update your code to use .webp instead of .png. Next.js Image component handles this automatically with formats: [&#39;image/webp&#39;].</p><p>We reduced our public folder from 11MB to 3.7MB.</p><h3>The Complete Dockerfile</h3><p>With all optimizations applied (including node-prune and distroless):</p><pre># =============================================================================<br># Stage 1: Dependencies<br># =============================================================================<br>FROM node:24-alpine AS deps<br>RUN apk add --no-cache libc6-compat<br>RUN corepack enable &amp;&amp; corepack prepare pnpm@10.22.0 --activate<br>WORKDIR /app<br>COPY package.json pnpm-lock.yaml ./<br>RUN pnpm install --prod --frozen-lockfile<br># =============================================================================<br># Stage 2: Builder<br># =============================================================================<br>FROM node:24-alpine AS builder<br>RUN corepack enable &amp;&amp; corepack prepare pnpm@10.22.0 --activate<br>WORKDIR /app<br>COPY --from=deps /app/node_modules ./node_modules<br>COPY . .<br>COPY .env.build ./.env<br>RUN pnpm install --frozen-lockfile<br>ENV NODE_ENV=production<br>RUN pnpm build<br># Remove junk from traced node_modules<br>RUN apk add --no-cache curl &amp;&amp; \<br>    curl -sf https://gobinaries.com/tj/node-prune | sh &amp;&amp; \<br>    node-prune .next/standalone/node_modules<br><br># =============================================================================<br># Stage 3: Runner (Production) - Distroless<br># =============================================================================<br>FROM gcr.io/distroless/nodejs22-debian12 AS runner<br>WORKDIR /app<br>ENV NODE_ENV=production<br>ENV PORT=3000<br># Distroless runs as nonroot (uid 65532) by default<br>COPY --from=builder --chown=65532:65532 /app/.next/standalone ./<br>COPY --from=builder --chown=65532:65532 /app/.next/static ./.next/static<br>COPY --from=builder --chown=65532:65532 /app/public ./public<br>USER nonroot<br>EXPOSE 3000<br>CMD [&quot;server.js&quot;]</pre><h3>The Results</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/658/1*HCW_vEeoTrPhWXQnrkVhog.png" /></figure><h3>Quick Verification Commands</h3><pre># Build the image<br>docker build -t myapp:latest .<br># Check the size<br>docker images myapp:latest<br># Inspect layers<br>docker history myapp:latest<br># Run locally<br>docker run -p 3000:3000 myapp:latest<br># Security scan<br>docker scout cves myapp:latest</pre><h3>Optimization Checklist</h3><p>Before shipping to production:</p><p><strong>Docker Basics:</strong></p><ul><li>Multi-stage build (deps → builder → runner)</li><li>.dockerignore excludes node_modules, .next, .git, .env files</li><li>Only .env.build (public vars) copied to image</li><li>Runtime secrets via orchestrator, not baked in</li><li>Non-root user in production stage</li><li>--frozen-lockfile for reproducible builds</li><li>Layer caching optimized (copy deps first)</li></ul><p><strong>Next.js Configuration:</strong></p><ul><li>output: &#39;standalone&#39; in next.config.js</li><li>serverExternalPackages for server-only deps</li><li>optimizePackageImports for barrel-export packages</li></ul><p><strong>Advanced Optimizations:</strong></p><ul><li>Alpine or Distroless base image</li><li>node-prune to remove junk from node_modules</li><li>Audit and remove unused packages</li><li>Convert images to WebP format</li><li>pnpm for faster, smaller installs</li></ul><h3>Final Thoughts</h3><p>Every megabyte in your Docker image costs real money and time. In Kubernetes, it compounds:</p><ul><li>Larger images = slower pulls = slower scaling</li><li>Slower scaling = missed SLAs during traffic spikes</li><li>More storage = higher registry and node costs</li></ul><p>We went from dreading deploys to shipping 15+ times per day. The 2-hour investment in optimizing our Dockerfile pays dividends on every single deploy.</p><p>These techniques aren’t Next.js-specific. Multi-stage builds, Alpine base images, and layer caching apply to any Docker workflow. Start with the biggest wins (Alpine + multi-stage), then iterate.</p><p>Your CI/CD pipeline will thank you.</p><p><strong>Ready to Build the Future of Voice?</strong></p><p>We are building the most responsive and natural-sounding voice agents at <strong>Zingaro AI</strong>. Whether you are a developer looking to experiment or a business ready to scale, we have the infrastructure you need.</p><p>🚀 <strong>Start building today:</strong> Check out <a href="https://zingaro.ai/">Zingaro.ai</a> and sign up for an account.</p><p>💼 <strong>For Enterprise Inquiries:</strong> If you need custom solutions or high-volume processing, reach out to our team directly at <strong>sales@zingaro.ai</strong>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35fa8ea4160d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Instantly Generate Dynamic AI Forms with OpenAI Structured Outputs and FastHTML]]></title>
            <link>https://medium.com/@pashashaik/instantly-generate-dynamic-ai-forms-with-openai-structured-outputs-and-fasthtml-fbc5eed3865c?source=rss-492b6b477184------2</link>
            <guid isPermaLink="false">https://medium.com/p/fbc5eed3865c</guid>
            <category><![CDATA[genarative-ai]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Pasha Shaik]]></dc:creator>
            <pubDate>Wed, 21 Aug 2024 11:24:33 GMT</pubDate>
            <atom:updated>2024-08-21T11:24:33.016Z</atom:updated>
            <content:encoded><![CDATA[<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F9mek_So_QJE%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D9mek_So_QJE&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F9mek_So_QJE%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ebaf02b029d88ce4ebd215fdd1738e0/href">https://medium.com/media/7ebaf02b029d88ce4ebd215fdd1738e0/href</a></iframe><p>Imagine creating dynamic forms in seconds, tailored to your specific needs, with just a simple text prompt. No more manually coding each field, setting up validation, or worrying about the layout. With the power of OpenAI and Python, you can now generate complex forms dynamically, straight from a text prompt. In this blog post, I’ll guide you through how to build this powerful tool that transforms natural language into fully functional HTML forms.</p><p>This blog post is inspired by my recent project, which you can check out <a href="https://github.com/pashanitw/ai-forms">here</a>.</p><h4>The Building Blocks</h4><p>Here’s the stack and libraries we’re using:</p><ul><li><strong>OpenAI</strong>: The AI model that parses your text prompt and returns structured data for form generation.</li><li><strong>Pydantic</strong>: For defining the schema and ensuring the data returned by the AI fits the expected format.</li><li><strong>FastHTML</strong>: FastHTML is a cutting-edge web framework that allows us to create forms entirely in Python, without the need for JavaScript or HTML.</li></ul><h4>Step-by-Step Guide to Building Your Dynamic Form Generator</h4><h4>Step 1: Define the Form Structure</h4><p>We start by defining the data structure for our forms using Pydantic models. This structure includes various field types (text, number, date, etc.) and allows for the specification of options for fields like dropdowns or checkboxes.</p><pre>from pydantic import BaseModel, Field<br>from enum import Enum<br>from typing import Optional, List<br><br>class TypeEnum(str, Enum):<br>    text = &quot;text&quot;<br>    number = &quot;number&quot;<br>    date = &quot;date&quot;<br>    radio = &quot;radio&quot;<br>    checkbox = &quot;checkbox&quot;<br>    select = &quot;select&quot;<br>    textarea = &quot;textarea&quot;<br><br>class Options(BaseModel):<br>    label: str = Field(description=&quot;Label for the option&quot;)<br>    value: str = Field(description=&quot;Value for the option&quot;)<br><br>class FormField(BaseModel):<br>    label: str = Field(description=&quot;Field label&quot;)<br>    type: TypeEnum = Field(description=&quot;Field type&quot;)<br>    name: str = Field(description=&quot;Unique field name&quot;)<br>    required: bool = Field(description=&quot;Is the field required?&quot;)<br>    placeholder: Optional[str] = Field(description=&quot;Placeholder text for input fields&quot;)<br>    options: Optional[List[Options]] = Field(description=&quot;Options for radio, checkbox, and select fields&quot;)<br><br>class DynamicForm(BaseModel):<br>    title: str = Field(description=&quot;Form title&quot;)<br>    fields: List[FormField] = Field(description=&quot;List of form fields&quot;)</pre><p>As you can see from the Pydantic structure above, it covers nearly all use cases. If you think I’ve missed anything, feel free to let me know in the comments!</p><h4>Step 2: Create the Dynamic Form</h4><p>The structured output from OpenAI is in the above Pydantic format, which we use to generate the actual HTML form. We iterate through the fields, creating the appropriate HTML elements for each field type. The exciting part is that with FastHTML, you can write everything in pure Python to render the form — no JavaScript or HTML is required. In fact, the entire code is contained within a single file.</p><pre>def create_dynamic_form(form_data: DynamicForm | None):<br>    if dynamic_form_data is None:<br>        return None<br><br>    fields = []<br><br>    for field in form_data.fields:<br>        field_type = field.type<br>        if field_type in [&quot;text&quot;, &quot;number&quot;, &quot;date&quot;]:<br>            fields.append(<br>                Div(<br>                    Label(field.label, _for=field.name),<br>                    Input(<br>                        type=field_type,<br>                        name=field.name,<br>                        placeholder=field.placeholder,<br>                        required=field.required,<br>                    ),<br>                    style=&quot;margin-bottom: 15px;&quot;,<br>                )<br>            )<br>        elif field_type == &quot;select&quot;:<br>            options = [<br>                Option(value=opt.value, label=opt.label) for opt in field.options<br>            ]<br>            fields.append(<br>                Div(<br>                    Label(field.label, _for=field.label),<br>                    Select(*options, name=field.name, required=field.required),<br>                    style=&quot;margin-bottom: 15px;&quot;,<br>                )<br>            )<br>        elif field_type == &quot;checkbox&quot;:<br>            checkboxes = [<br>                Div(<br>                    Input(<br>                        type=&quot;checkbox&quot;,<br>                        name=field.name,<br>                        value=opt.value,<br>                        id=f&quot;{field.name}_{opt.value}&quot;,<br>                    ),<br>                    Label(opt.label, _for=f&quot;{field.name}_{opt.value}&quot;),<br>                )<br>                for opt in field.options<br>            ]<br>            fields.append(<br>                Div(Label(field.label), Div(*checkboxes), style=&quot;margin-bottom: 15px;&quot;)<br>            )<br>        elif field_type == &quot;radio&quot;:<br>            radios = [<br>                Div(<br>                    Input(<br>                        type=&quot;radio&quot;,<br>                        name=field.name,<br>                        value=opt.value,<br>                        id=f&quot;{field.name}_{opt.value}&quot;,<br>                        required=field.required,<br>                    ),<br>                    Label(opt.label, _for=f&quot;{field.name}_{opt.label}&quot;),<br>                )<br>                for opt in field.options<br>            ]<br>            fields.append(<br>                Div(Label(field.label), Div(*radios), style=&quot;margin-bottom: 15px;&quot;)<br>            )<br>        elif field_type == &quot;textarea&quot;:<br>            fields.append(<br>                Div(<br>                    Label(field.label, _for=field.name),<br>                    Textarea(<br>                        name=field.name,<br>                        placeholder=field.placeholder,<br>                        required=field.required,<br>                    ),<br>                    style=&quot;margin-bottom: 15px;&quot;,<br>                )<br>            )<br><br>    form = Form(<br>        *fields,<br>        Button(&quot;Submit&quot;, type=&quot;submit&quot;, style=&quot;margin-top: 20px;&quot;),<br>        method=&quot;post&quot;,<br>        action=&quot;/submit&quot;,<br>    )<br><br>    return Container(H1(form_data.title), form)</pre><p>Don’t worry if the code above seems confusing — it’s just the FastHTML approach to rendering HTML. You can learn more about FastHTML by visiting their website at <a href="http://fastht.ml/">fastht.ml</a>.</p><h4>Step 3: Generate Form Data with OpenAI</h4><p>Next, we use OpenAI to generate the form structure based on a text prompt. The AI reads the prompt, understands the form requirements, and outputs the corresponding form structure in the above Pydantic format.</p><pre>from openai import OpenAI<br>from dotenv import load_dotenv<br>from textwrap import dedent<br><br>load_dotenv()<br>client = OpenAI()<br><br>def get_form_response(prompt):<br>    system_prompt = &quot;&quot;&quot;<br>        You are an AI that generates HTML forms based on user prompts.<br>        Your task is to take a description of a form and output the corresponding form fields and structure.<br>    &quot;&quot;&quot;<br>    completion = client.beta.chat.completions.parse(<br>        model=&quot;gpt-4o-2024-08-06&quot;,<br>        messages=[<br>            {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: dedent(system_prompt)},<br>            {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt},<br>        ],<br>        response_format=DynamicForm,<br>    )<br>    return completion.choices[0].message</pre><p>The key point to note here is the response format, which we’re defining using the Above Pydantic class. This explicitly instructs OpenAI to generate output in this specific format.</p><h4>Step 4: Deploy and Run the Web Application</h4><p>Finally, we wrap everything in a web application using FastHTML. The app takes user input, passes it to OpenAI to generate the form, and then dynamically displays the generated form.</p><pre>@app.get(&quot;/&quot;)<br>def get():<br>    prompt_area = Form(H2(&quot;Enter your prompt here to create a dynamic form&quot;), Textarea(name=&quot;prompt&quot;), Button(&quot;Create Form&quot;, hx_post=&quot;/update-prompt&quot;))<br>    form_area = Div(id=&quot;form_area&quot;)<br>    return Container(prompt_area, form_area)<br><br>@app.post(&quot;/update-prompt&quot;)<br>async def update_prompt(request):<br>    form_data = await request.form()<br>    form_prompt = form_data.get(&quot;prompt&quot;, &quot;&quot;)<br>    if form_prompt:<br>        dynamic_form_data = get_form_response(form_prompt)<br>        return Div(create_dynamic_form(dynamic_form_data), id=&quot;form_area&quot;)<br>    return Div(&quot;Error generating form. Please try again.&quot;, id=&quot;form_area&quot;)</pre><p><strong>Bringing It All Together</strong></p><p>This tool can be invaluable in any scenario where forms are frequently customized or generated on the fly, from admin panels to user surveys. The beauty lies in the seamless integration of AI to automate the creation process, freeing developers from repetitive tasks and allowing them to focus on more creative aspects of their work.</p><p>The full code is available <a href="https://github.com/pashanitw/ai-forms">here</a>, and I encourage you to experiment with it and adapt it to your projects.</p><p>If you have any questions or want to share your experiences, feel free to drop a comment below or connect with me on <a href="https://www.linkedin.com/in/pasha-shaik/">LinkedIn</a>. Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fbc5eed3865c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Natural Language Generation from Scratch in Large Language Models with PyTorch]]></title>
            <link>https://medium.com/@pashashaik/natural-language-generation-from-scratch-in-large-language-models-with-pytorch-4d9379635316?source=rss-492b6b477184------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d9379635316</guid>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[naturallanguageprocessing]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Pasha Shaik]]></dc:creator>
            <pubDate>Wed, 14 Feb 2024 17:35:57 GMT</pubDate>
            <atom:updated>2024-02-14T17:35:57.763Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/784/1*cOawUwSSpBAVZp9x6cXE-A.png" /></figure><p>If you’re already working with large language models (LLMs), chances are you’re using natural language generation (NLG) in some way. Maybe you’re using Hugging Face models, either with the easy-to-use Hugging Face pipeline or the <strong>model.generate</strong> function. But today, we’re going to look closer and really get into how to create text [tokens] right from the beginning.</p><p>Large language models are autoregressive, meaning they generate each word based on the previous ones and continue this process until certain criteria are met. This article aims to explore and code these criteria from scratch. While we’ll still be utilizing Hugging Face models, we’ll craft our own generate method. This approach will help us gain insights into what occurs behind the scenes.</p><p>I’ve also shared a fully functional example on Colab. You can find the link below.</p><h4>Why you need to understand this ?</h4><ol><li>deeper understanding of how the large language models work</li><li>you can customize the generation process</li><li>can lead to new generation methods</li><li>also helps you how to prompt these models better</li></ol><p>If you’ve ever built a large language model (LLM) from scratch, you probably know what the final result is like. If you haven’t built an LLM model from the beginning, I’ll start with the basic ideas first.</p><p>In any deep learning model, there are two main components: the input and the output. For large language models (LLMs), the input typically consists of a batch size and a sequence of words, often referred to as the ‘prompt.’ The length of this prompt sequence can vary based on our choice — we decide how many words to use as input. To show how this works in practice, I’ll use the Zephyr 7B model from Hugging Face as an example for demonstration.</p><h4>Tokenization:-</h4><p>Let’s import the model from huggingface !</p><pre># Define the model name to be used from Hugging Face<br>model_name = &quot;HuggingFaceH4/zephyr-7b-beta&quot;<br><br># Initialize tokenizer for converting text to model-understandable format<br>tokenizer = AutoTokenizer.from_pretrained(model_name)<br><br># Load the causal language model with auto device mapping and custom settings<br>model = AutoModelForCausalLM.from_pretrained(<br>    model_name,<br>    device_map=&#39;auto&#39;,  # Automatically assign model to best available device<br>    trust_remote_code=True,  # Allow use of custom layers from Hugging Face Hub<br>    torch_dtype=torch.float16,  # Set model parameters to float16 for efficiency<br>)<br><br># Set the model to evaluation mode<br>model.eval()<br><br># Define the prompt text to generate language from<br>prompt = &quot;write a story about a cat&quot;</pre><p>Let’s tokenize the prompt. Tokenizing is a process where we break down text into smaller parts. In simple terms, this can mean dividing the text into individual words or sometimes splitting a word into several parts. For our purposes, let’s think of it as simply splitting the text into words. Now, let’s see how the text looks once it’s tokenized.</p><pre>tokens = tokenizer.tokenize(prompt)<br>print(tokens)<br><br># [&#39;▁write&#39;, &#39;▁a&#39;, &#39;▁story&#39;, &#39;▁about&#39;, &#39;▁a&#39;, &#39;▁cat&#39;]</pre><p>Our prompt consists of six words. Let’s proceed to encode it. Encoding refers to assigning a unique number to each word.</p><pre>input_ids = tokenizer.encode(prompt, return_tensors=&quot;pt&quot;).to(device)<br>print(input_ids)<br>print(input_ids.shape)<br><br># tensor([[   1, 3324,  264, 2838,  684,  264, 5255]], device=&#39;cuda:0&#39;)<br># torch.Size([1, 7])</pre><p>Our prompt contains just six words, yet the encoded prompt shows a length of seven. Why is that? It’s because we’re adding a start token at the beginning of the sequence.</p><p>Let’s use the model’s generate function, a method provided by Hugging Face, to produce the output. This will be the only instance where we utilize the predefined generate function. Moving forward, we’ll switch to our custom generate function that we will develop.</p><pre>output = model.generate(input_ids, max_length=50)<br>generated_text = tokenizer.decode(output[0])<br>print(generated_text)<br><br># &lt;s&gt; write a story about a cat who solves crimes\n\nTitle: The Purrfect Detective\n\nChapter One: The Case of the Missing Mouse\n\nIn the quiet neighborhood of Maple Street, a cat named Oscar was&#39;</pre><p>In the output, we notice that the model repeats our prompt for the first seven words. Starting with the eighth word, it begins to generate new content. This is where the model starts creating its own text, moving beyond just repeating what we gave it.</p><p>Now we understand how the model produces output: it uses our prompt, applies internal attention mechanisms, and generates the next token, continuing this process. Next, we’ll skip the Hugging Face generate method. Instead, we’ll directly input our data into the model for a forward pass and analyze the resulting output.</p><h4>Forward Pass:-</h4><p>In a forward pass for large language models (LLMs), the model receives input text, such as a sentence or phrase, processes it systematically, and predicts the next word. This forward pass needs to be repeated multiple times until we either encounter the end-of-sentence token (&lt;/s&gt;) or reach the predefined maximum length for the text sequence.</p><pre><br>output = model(input_ids)<br>print(output.keys())<br><br># odict_keys([&#39;logits&#39;, &#39;past_key_values&#39;])</pre><h4>what we just did ?</h4><p>We feed the model our start token along with six words, making a total of seven tokens including start token, to predict the eighth token. The result we get is called <strong>logits</strong>. Now, let’s examine the shape of these <strong>logits</strong> to understand them better.</p><p>The shape of <strong>logits</strong> will give us insight into the dimensions of the output, such as how many tokens were processed and the range of possible word predictions for each token.</p><pre>output.logits.shape<br><br># torch.Size([1, 7, 32000])</pre><p>The logits shape `torch.Size([1, 7, 32000])` means:</p><ul><li><strong>1 sequence</strong> is being processed or you can think of a batch size.</li><li><strong>7 tokens</strong> in the sequence, including your input.</li><li><strong>32,000 possible</strong> words or tokens that the model predicts for each of these 7 tokens. which is also called vocabulary size</li></ul><p>The <strong>logits</strong> shape <strong>torch.Size([1, 7, 32000])</strong> indicates one sequence with seven tokens and 32,000 word predictions for each token. Next, we’ll explore how to pick the next token from these 32,000 options.</p><h4>Decoding Strategies</h4><ol><li><strong>Greedy Decoding</strong></li><li><strong>Beam Search</strong></li><li><strong>Top-K Sampling</strong></li><li><strong>Nucleus Sampling</strong></li></ol><h4>Let’s discuss all these strategies and implement them from scratch using pytorch</h4><h3>1. Greedy Decoding</h3><p>Greedy decoding is probably the easiest decoding strategy.At each time step, when we input our sequence into the model, we receive a probability distribution across the entire vocabulary(30K tokens in our case). Here, we consistently select the token with the highest probability at every step.</p><p>Let’s code this in pytorch !</p><pre>def greedy_decoding(input_ids, max_tokens=300):<br>    with torch.inference_mode():<br>        for _ in range(max_tokens):<br>            # input_ids shape: [1, current_sequence_length]<br>            outputs = model(input_ids)<br><br>            # outputs.logits shape: [1, current_sequence_length, vocab_size]<br>            next_token_logits = outputs.logits[:, -1, :]<br>            # next_token_logits shape: [1, vocab_size]<br><br>            next_token = torch.argmax(next_token_logits, dim=-1)<br>            # next_token shape: [1] (the most probable next token ID)<br>            <br>            # stop generation if the model produces the end of sentence &lt;/s&gt; token <br>            if next_token == tokenizer.eos_token_id:<br>                break<br><br>            # rearrange(next_token, &#39;c -&gt; 1 c&#39;): changes shape to [1, 1] for concatenation<br>            input_ids = torch.cat([input_ids, rearrange(next_token, &#39;c -&gt; 1 c&#39;)], dim=-1)<br>            # input_ids shape after concatenation: [1, current_sequence_length + 1]<br><br>        generated_text = tokenizer.decode(input_ids[0])<br>        # input_ids[0] shape for decoding: [current_sequence_length]<br><br>    return generated_text<br><br># Call the function with initial input_ids<br>print(greedy_decoding(input_ids))<br><br># &lt;s&gt; write a story about a cat <br># who solves crimes\n\nTitle: The Purrfect Detective\n\nChapter One: The Case of the Missing Mouse\n\nIn the quiet neighborhood of Maple Street, a cat named Oscar was known for his sharp senses and quick reflexes. Oscar was not an ordinary cat; he was a detective. He had a reputation for solving mysteries that left the other cats in awe.\n\nOne fine day, Oscar received a call from his friend, the mouse, Timmy. Timmy was missing, and Oscar was the only one who could help find him. Oscar knew that this was his chance to&#39;</pre><p>The line “<strong><em>next_token = torch.argmax(next_token_logits, dim=-1)</em></strong>” means that we’re selecting the token with highest probability.</p><p>Then, in the next line, we take this chosen token and add it to our input sentence to keep generating text. So, we’re building our text step by step, always choosing the word that seems the most probable according to the model.</p><p>For a straightforward explanation, please note that this code is designed to work with a batch size of one. If you intend to use it for batch decoding, some modifications may be necessary.</p><h4>Why Greedy Decoding is Not best choice ?</h4><ol><li><strong>Not the Best Choice:</strong> Greedy decoding often doesn’t produce the best overall text. It only picks the most likely next word each time, which might not make the best sentence when you look at it as a whole<strong>.</strong></li><li><strong>Repetitive and Predictable:</strong> It tends to create text that’s not very varied or interesting. Since it always goes for the most obvious next word, the results can be quite generic.</li><li><strong>Can’t Fix Mistakes:</strong> Once greedy decoding makes a mistake, it can’t go back and fix it. This means that one wrong word choice can lead to more errors down the line.</li><li><strong>Limited Exploration:</strong> Greedy decoding doesn’t consider many different options or paths. It just sticks to a narrow set of choices, which means it might miss out on better or more suitable ways to complete the text.</li></ol><p>In summary, greedy decoding is a quick and straightforward method, but it’s limited in its ability to create diverse, high-quality text, especially for complex or longer content.</p><h3>2. Beam Search:</h3><p>The fundamental idea of beam search is exploring multiple paths instead of just single one.: Unlike greedy decoding, which only keeps the single best path (the most probable next word) at each step, beam search keeps track of the <strong><em>k</em></strong> most probable paths at each step. This means at every step in the sequence, it doesn’t just consider the single most likely next word, but the <strong><em>k</em></strong> most likely next words.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NAABkygo55OdLMp_Ii_V6g.png" /><figcaption>beam search</figcaption></figure><p>The diagram illustrates how beam search operates with a beam size of 2. Let’s walk through the steps depicted in the diagram, which show the progression of the search through different timesteps:</p><p><strong>Timestep 1:</strong></p><ul><li><strong>Start:</strong> The process begins with the <strong>&lt;START&gt;</strong> token.</li><li><strong>First Word Choices:</strong> Two paths are considered based on the first word choice, <strong>“the”</strong> and <strong>“a”</strong>. These are the two most probable words following the <strong>&lt;START&gt;</strong> token.</li></ul><p><strong>Timestep 2:</strong></p><ul><li><strong>Second Word Choices:</strong> For each of the initial two paths, the model then considers the next set of words. For the path starting with <strong>“the”</strong>, it explores <strong>“poor”</strong> and <strong>“people”</strong>. For the path starting with <strong>“a”</strong>, it explores <strong>“poor”</strong> and <strong>“person”</strong>.</li><li><strong>Pruning to Beam Size:</strong> After considering the probabilities of these expansions, the algorithm keeps the two most probable paths. Let’s assume these are <strong>“the poor”</strong> and <strong>“a poor”</strong>.</li></ul><p><strong>Timestep 3:</strong></p><ul><li><strong>Third Word Choices:</strong> From <strong>“the poor”</strong>, the model explores further expansions and similarly from <strong>“a person”</strong>. For <strong>“the poor”</strong>, the next words considered are <strong>“are”</strong> and <strong>“don’t”</strong>. For <strong>“a poor”</strong>, the model looks at <strong>“person”</strong> and <strong>“but”</strong>. However, <strong>“a poor”</strong> path would have a lower probability and be pruned.</li></ul><p><strong>Timestep 4:</strong></p><ul><li><strong>Fourth Word Choices:</strong> Continuing from the remaining high-probability paths, <strong>“the poor are”</strong> leads to <strong>“always”</strong> and <strong>“not”</strong>, while <strong>“the poor don’t”</strong> leads to <strong>“have”</strong> and <strong>“take”.</strong> The top two paths with the highest cumulative probability are kept, and the rest are pruned.</li></ul><p><strong>Timestep 5:</strong></p><ul><li>Fifth Word Choices: Now, the model expands <strong>“the poor are not”</strong> and <strong>“the poor don’t have”</strong>. For <strong>“not”</strong>, it considers <strong>“in”</strong> and <strong>“with”,</strong> and for <strong>“have”</strong>, it considers <strong>“any”</strong> and <strong>“enough”</strong>.</li></ul><p>At each step, only the two most probable paths are kept (as per the beam size of 2), and all other possibilities are discarded. This process continues until a stopping criterion is met, which can be the model generating an end-of-sequence token or reaching a maximum sequence length. The final output would be the sequence with the highest overall probability from the last set of paths.</p><p>First, let’s implement beam search in PyTorch, and then I’ll delve into the specifics.</p><pre>from einops import rearrange<br>import torch.nn.functional as F<br><br>def beam_search(input_ids, max_tokens=100, beam_size=2):<br>    # Initialize the scores for each beam with zeros. Shape: [beam_size]<br>    beam_scores = torch.zeros(beam_size).to(device)<br>    <br>    # Duplicate the initial sequence for each beam. Shape: [beam_size, seq_length]<br>    beam_sequences = input_ids.clone()<br>    <br>    # Create a boolean mask to keep track of active beams. Shape: [beam_size]<br>    active_beams = torch.ones(beam_size, dtype=torch.bool)<br>   <br>    <br>    for step in range(max_tokens):<br>        # Generate model outputs for the current sequences. <br>        # The model is expected to handle batched input, hence the shape of beam_sequences is [beam_size, current_seq_length].<br>        outputs = model(beam_sequences)<br>        <br>        # Extract the last logits from the output to get the probabilities for the next token. Shape: [beam_size, vocab_size]<br>        logits = outputs.logits[:, -1, :]<br>        probs = F.softmax(logits, dim=-1)<br>        <br>        # Calculate the score for each beam and token by flattening the probabilities and selecting the top ones.<br>        # The flattened shape is [beam_size * vocab_size], from which we select the top beam_size scores.<br>        top_scores, top_indices = torch.topk(probs.flatten(), k=beam_size, sorted=False)<br>       <br>        # Map flat indices back to beam and token indices.<br>        # beam_indices is the index in the beam, shape: [beam_size]<br>        # token_indices is the index of the token in the vocabulary, shape: [beam_size]<br>        beam_indices = top_indices // probs.shape[-1]<br>        token_indices = top_indices % probs.shape[-1]<br>        <br>        # Update the sequences with the new tokens at the end. Shape after update: [beam_size, current_seq_length + 1]<br>        # This concatenates the best token for each beam to the end of the sequences.<br>        beam_sequences = torch.cat([<br>            beam_sequences[beam_indices],<br>            token_indices.unsqueeze(-1)<br>        ], dim=-1)<br><br>        # Update the beam scores with the top scores. Shape: [beam_size]<br>        beam_scores = top_scores<br>        <br>        # Check for the end-of-sequence tokens and update the active beams.<br>        # If a beam produces an EOS token, it is marked as inactive.<br>        active_beams = ~(token_indices == tokenizer.eos_token_id)<br>        <br>        # If all beams are inactive, exit the loop.<br>        if not active_beams.any():<br>            print(&quot;no active beams&quot;)<br>            break<br>    <br>    # Select the beam with the highest score as the best sequence. Shape: [best_seq_length]<br>    best_beam = beam_scores.argmax()<br>    best_sequence = beam_sequences[best_beam]<br>    <br>    # Decode the best sequence to generate the final text.<br>    generated_text = tokenizer.decode(best_sequence)<br>    <br>    return generated_text</pre><p>If the above code seems unclear, here’s a simplified version of the beam search algorithm.</p><h4>Starting Point</h4><p>Let’s begin with two sequences of token IDs:</p><pre>input_ids = torch.tensor([[1, 2, 3, 0], [1, 2, 3, 2]])</pre><p>The beam size determines how many sequences we’ll keep track of at each step:</p><pre>beam_size = 2</pre><p>In this case, we’ll explore the two most promising continuations for our sequences.</p><h4>Vocabulary and Probabilities</h4><p>We have a small example vocabulary and corresponding probabilities for the next token, given by a model:</p><pre>vocab = [0, 1, 2, 3]<br>probs = torch.tensor([<br>    [0.2, 0.3, 0.5, 0.1], [0.2, 0.2, 0.2, 0.2]<br>])</pre><p>Here, each sublist in probs represents the model&#39;s estimated probability of each vocabulary token being the next in the sequence. For instance, the model believes there&#39;s a 50% chance that the next token after [1, 2, 3, 0] is 2.</p><h4>Selecting the Top Candidates</h4><p>Next, we flatten the probability tensor and pick the top beam_size probabilities:</p><pre>top_scores, top_indices = torch.topk(probs.flatten(), k=beam_size, sorted=False)</pre><p>The flatten function turns our 2D probabilities array into a 1D array, and torch.topk retrieves the highest values and their indices.</p><h4>Mapping Back to Beams and Tokens</h4><p>We then determine which beam each top score came from and the token associated with that score:</p><pre>beam_indices = top_indices // len(vocab)<br>token_indices = top_indices % len(vocab)</pre><p>The division and modulus operations help us map the flat index back to a specific beam and token ID.</p><h4>Updating the Sequences</h4><p>Finally, we update our sequences with the new tokens:</p><pre>beam_sequences = torch.cat([<br>    input_ids[beam_indices],<br>    token_indices.unsqueeze(-1)<br>], dim=-1)</pre><p>Let’s put all the code together so you can run it as a whole in one cell to understand how the above algorithm works.</p><pre>input_ids = torch.tensor([[1, 2, 3, 0], [1,2, 3, 2]])<br>beam_size = 2<br>vocab = [0, 1 , 2, 3]<br>probs = torch.tensor([<br>    [0.2, 0.3, 0.5, 0.1], [0.2, 0.2, 0.2, 0.2]<br>])<br>top_scores, top_indices = torch.topk(probs.flatten(), k=beam_size, sorted=False)<br>print(top_scores, top_indices) # tensor([0.5000, 0.3000]) tensor([2, 1])<br>beam_indices = top_indices // len(vocab)<br>token_indices = top_indices % len(vocab)<br>print(beam_indices,token_indices) # tensor([0, 0]) tensor([2, 1])<br>beam_sequences = torch.cat([<br>    input_ids[beam_indices],<br>    token_indices.unsqueeze(-1)<br>], dim=-1)<br>print(beam_sequences) # tensor([[1, 2, 3, 0, 2], [1, 2, 3, 0, 1]])</pre><p>After running this piece of code, beam_sequences contains the updated sequences with the new tokens added at the end. This process would be repeated for each new token we want to generate, gradually building up more complete sequences.</p><h3><strong>3. Top-K Sampling</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/723/1*7COE6WzNb2MA5snwi-Ofhg.png" /></figure><p>The concept of Top-K sampling involves sampling only from the top K tokens in the probability distribution. Let’s say we set K to 50; this means that when we predict the next token, we sort the entire 32,000-word vocabulary according to the softmax scores of our logits. Then, we retain only the top 50 options from the vocabulary and remove the rest of the items. Subsequently, we randomly select a token from the previously filtered list.</p><pre>def top_k_sampling(input_ids, max_tokens=100, top_k=50, temperature=1.0):<br>   for _ in range(max_tokens):<br>        # Temporarily disables gradient calculation to improve performance and reduce memory usage<br>        with torch.inference_mode():<br>            # Obtain the model&#39;s outputs based on the input IDs<br>            outputs = model(input_ids)<br>            <br>            # Extract logits of the next token<br>            next_token_logits = outputs.logits[:, -1, :]<br>            <br>            # Select the top K tokens from the probability distribution<br>            top_k_logits, top_k_indices = torch.topk(next_token_logits, top_k)<br>            <br>            # Apply softmax to convert logits to probabilities, with optional temperature scaling<br>            top_k_probs = F.softmax(top_k_logits / temperature, dim=-1)<br>            <br>            # Sample from the top K tokens to determine the next token<br>            next_token_index = torch.multinomial(top_k_probs, num_samples=1)<br>            <br>            # Map the sampled token back to its original index in the logits tensor<br>            next_token = top_k_indices.gather(-1, next_token_index)<br>            <br>            # Concatenate the new token to the sequence of input IDs<br>            input_ids = torch.cat([input_ids, next_token], dim=-1)<br>        <br>    # Decode the tensor of input IDs to a string of text<br>    generated_text = tokenizer.decode(input_ids[0])<br>    <br>    return generated_text</pre><p><strong>What is the problem with Top-K sampling ?</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yH8wO72IGjQZR4wfNEFbfA.png" /></figure><ol><li><strong>Dynamic Probability Distributions:</strong> When we use Top-K sampling, the list of possible next words (tokens) and their chances of being picked can change a lot each time we choose a new word. Sometimes, many words have a similar chance of being chosen, and other times, one or a few words are much more likely than the others<strong>.</strong></li><li><strong>When the List is Flat (Many Words Have Similar Chances):</strong> Imagine we have a big basket of fruits, and you’re almost equally likely to pick any fruit because you like them all pretty much the same. If you’re only allowed to choose from the top 5 fruits in the basket, you might miss out on a lot of other fruits you also like just as much. This is like when the probability distribution is flat, and limiting ourselves to the top K options (like the top 5 fruits) means we ignore many other good choices.</li><li><strong>When the List is Peaky (One or a Few Words are Much More Likely):</strong> Imagine you’re at a buffet with one dish you absolutely love and many others that are just okay. If you’re allowed to choose from a very large selection, including all the okay dishes alongside your favorite, the presence of so many less appealing options could lead you to pick something less satisfying by chance. In a peaky distribution, where one or a few words are much more likely to be the next correct choice, setting a high K allows many less probable (less appealing) options to be considered. This increases the chances of selecting a word that isn’t the best fit, simply because it’s among the many options available, diluting the likelihood of choosing the most appropriate or expected word based on the context.</li></ol><p>This issue can be addressed by the subsequent technique we’ll explore, known as <strong>Top-P (Nucleus Sampling)</strong>.</p><h3><strong>4. Top-P (Nucleus) </strong>Sampling:</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O_Gf6BHSvHn18uzT4bPuGQ.png" /></figure><p>Instead of selecting a fixed number of top tokens (like the top 50 in Top-K sampling), Top-P sampling chooses tokens based on their cumulative probability. It looks at the list of all possible next tokens, ranked by their probability, and adds them up from the most likely to the least likely until the total probability reaches a threshold P (like 0.9 or 90%).</p><ul><li><strong>Cumulative Probability Mass:</strong> Imagine you have a pie chart representing the probability of each token being the next word, with the whole pie being 100%. Top-P sampling slices this pie from the most significant piece (most probable token) onwards, stopping when the combined slice reaches the P threshold. This cumulative slice represents the “mass” where the probabilities are concentrated.</li><li><strong>Adapting to the Distribution (Varies K):</strong> The key advantage of Top-P sampling is its flexibility. The number of tokens selected (equivalent to K in Top-K sampling) automatically adjusts based on how spread out or concentrated the probabilities are. If a few tokens hold most of the probability mass (a “peaky” distribution), only those will be considered, resulting in a smaller “K”. If the probability is more evenly distributed (“flatter”), more tokens are included to reach the P threshold, resulting in a larger “K”.</li><li><strong>Uniformity of Pt:</strong> “Pt” refers to the probability distribution at a given time step (t) for selecting the next token. Top-P sampling dynamically adjusts the number of options considered by directly responding to the shape of this distribution. When Pt is uniform (flatter), indicating many plausible next tokens, more tokens are included to reach the cumulative probability threshold P. When Pt is less uniform (peakier), fewer tokens are needed.</li></ul><pre>def top_p_sampling(input_ids, max_tokens=100, top_p=0.95):<br>    # Temporarily disables gradient calculation to improve performance and reduce memory usage<br>    with torch.inference_mode():<br>        for _ in range(max_tokens):<br>                # Obtain the model&#39;s outputs based on the input IDs<br>                outputs = model(input_ids)<br>                next_token_logits = outputs.logits[:, -1, :]<br>                <br>                # Sort the logits in descending order and apply softmax to get probabilities<br>                sorted_logits, sorted_indices = torch.sort(next_token_logits, descending=True)<br>                sorted_probabilities = F.softmax(sorted_logits, dim=-1) # Apply temperature<br>                <br>                # Calculate cumulative probabilities<br>                cumulative_probs = torch.cumsum(sorted_probabilities, dim=-1)<br>                <br>                # Identify and remove tokens with cumulative probability above the threshold (top_p)<br>                # Ensuring the first token is always selected by setting the first position to False<br>                sorted_indices_to_remove = cumulative_probs &gt; top_p<br>                sorted_indices_to_remove[..., 0] = False  # Keep the most probable token<br>                <br>                # Get the actual indices to remove from the original logits tensor<br>                indices_to_remove = sorted_indices[sorted_indices_to_remove]<br>                <br>                # Set the logits of removed tokens to -infinity to exclude them from sampling<br>                next_token_logits.scatter_(-1, indices_to_remove[None, :], float(&#39;-inf&#39;))<br>                <br>                # Re-calculate probabilities after filtering and sample from this distribution<br>                probs = F.softmax(next_token_logits, dim=-1)<br>                next_token = torch.multinomial(probs, num_samples=1)<br>                <br>                # Append the sampled token to the input IDs for the next iteration<br>                input_ids = torch.cat([input_ids, next_token], dim=-1)<br>        <br>        # Decode the tensor of input IDs to a string of text<br>        generated_text = tokenizer.decode(input_ids[0])<br>    <br>    return generated_text</pre><p>Decoding in natural language generation (NLG) remains a tough puzzle, but even small changes to how we choose words can make a big difference. These tweaks help us make the generated text more natural and are key to the latest breakthroughs in making computers talk more like humans.</p><h4>Colab Link:-</h4><p><a href="https://colab.research.google.com/drive/13L_PE7QBMCkIWFD9OzthD-oOGgGyz027?usp=sharing">https://colab.research.google.com/drive/13L_PE7QBMCkIWFD9OzthD-oOGgGyz027?usp=sharing</a></p><h3>Conclusion</h3><p>I hope you found this article insightful and valuable. If you enjoyed the content and wish to stay updated with future posts, please consider following me on <a href="https://www.linkedin.com/in/pasha-shaik/">LinkedIn</a>. Your support motivates me to continue sharing knowledge. Thank you for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d9379635316" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Part 1: Building Vision Transformer from Scratch: A PyTorch Deep Dive Plus a Teaser on LORA for…]]></title>
            <link>https://medium.com/@pashashaik/part-1-building-vision-transformers-from-scratch-a-pytorch-deep-dive-plus-a-teaser-on-lora-for-beef0f3aef5c?source=rss-492b6b477184------2</link>
            <guid isPermaLink="false">https://medium.com/p/beef0f3aef5c</guid>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[nlp]]></category>
            <dc:creator><![CDATA[Pasha Shaik]]></dc:creator>
            <pubDate>Wed, 01 Nov 2023 16:22:13 GMT</pubDate>
            <atom:updated>2023-11-01T19:36:43.602Z</atom:updated>
            <content:encoded><![CDATA[<h3>Part 1: Building Vision Transformer from Scratch: A PyTorch Deep Dive Plus a Teaser on LORA for Part 2</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mr46bo_Vj5K0nmt5ViEX8Q.png" /><figcaption>Vision Transformer</figcaption></figure><p>If you’ve delved into the realm of deep learning, you’re likely aware of the impact that transformer architectures have had on the field of artificial intelligence. These architectures stand at the core of numerous groundbreaking advancements in AI. In this Article, we will embark on an in-depth exploration, guiding you through the process of building Vision Transformers from the ground up.</p><p>This article is the first in a four-part series. The next one will show how to build ‘LoRa’ from scratch, for the Vision Transformer we are building here.</p><p>I’ve also shared a fully functional example on Colab. You can find the link below.</p><h4>Lets Define a configuration for the Vision Transformer model using a data class</h4><pre>@dataclass<br>class ModelArgs:<br>    dim: int = 256          # Dimension of the model embeddings<br>    hidden_dim: int = 512   # Dimension of the hidden layers<br>    n_heads: int = 8        # Number of attention heads<br>    n_layers: int = 6       # Number of layers in the transformer<br>    patch_size: int = 4     # Size of the patches (typically square)<br>    n_channels: int = 3     # Number of input channels (e.g., 3 for RGB images)<br>    n_patches: int = 64     # Number of patches in the input<br>    n_classes: int = 10     # Number of target classes<br>    dropout: float = 0.2    # Dropout rate for regularization</pre><h4>MultiHead Attention</h4><p>The MultiHeadAttention module in the provided code is an implementation of the multi-head self-attention mechanism, which stands as a fundamental component in transformer architectures. This self-attention mechanism empowers the model to weigh input elements differently, offering the capability to focus more intently on certain parts of the input when generating the output.</p><p>First, let’s code the multi-head attention block. Afterward, I’ll break down the key components in detail</p><pre><br>class MultiHeadAttention(nn.Module):<br>    def __init__(self, args:ModelArgs):<br>        super().__init__()<br>        self.n_heads = args.n_heads<br>        self.dim = args.dim<br>        self.head_dim = args.dim // args.n_heads<br>        <br>        # Linear projections for Q, K, and V<br>        self.wq = nn.Linear(self.dim, self.n_heads*self.head_dim, bias=False)<br>        self.wk = nn.Linear(self.dim, self.n_heads*self.head_dim, bias=False)<br>        self.wv = nn.Linear(self.dim, self.n_heads*self.head_dim, bias=False)<br>        self.wo = nn.Linear(self.n_heads*self.head_dim, self.dim, bias=False)<br>    <br>    def forward(self, x):<br>        b, seq_len, dim = x.shape  # b: batch size, seq_len: sequence length<br>        <br>        assert dim == self.dim, &quot;dim is not matching&quot;<br>        <br>        q = self.wq(x)  # [b, seq_len, n_heads*head_dim]<br>        k = self.wk(x)  # [b, seq_len, n_heads*head_dim]<br>        v = self.wv(x)  # [b, seq_len, n_heads*head_dim]<br>        <br>        # Reshape the tensors for multi-head operations<br>        q = q.contiguous().view(b, seq_len, self.n_heads, self.head_dim)  # [b, seq_len, n_heads, head_dim]<br>        k = k.contiguous().view(b, seq_len, self.n_heads, self.head_dim)  # [b, seq_len, n_heads, head_dim]<br>        v = v.contiguous().view(b, seq_len, self.n_heads, self.head_dim)  # [b, seq_len, n_heads, head_dim]<br>        <br>        # Transpose to bring the head dimension to the front<br>        q = q.transpose(1, 2)  # [b, n_heads, seq_len, head_dim]<br>        k = k.transpose(1, 2)  # [b, n_heads, seq_len, head_dim]<br>        v = v.transpose(1, 2)  # [b, n_heads, seq_len, head_dim]<br>        <br>        # Compute attention scores and apply softmax<br>        attn = torch.matmul(q, k.transpose(2, 3)) / math.sqrt(self.head_dim)  # [b, n_heads, seq_len, seq_len]<br>        attn_scores = F.softmax(attn, dim=-1)  # [b, n_heads, seq_len, seq_len]<br>        <br>        # Compute the attended features<br>        out = torch.matmul(attn_scores, v)  # [b, n_heads, seq_len, head_dim]<br>        out = out.contiguous().view(b, seq_len, -1)  # [b, seq_len, n_heads*head_dim]<br>        <br>        return self.wo(out)  # [b, seq_len, dim]<br></pre><p>The MultiHeadAttention module performing the following operations:</p><ol><li>Linear transformations of the input tensor into “query” (Q), “key” (K), and “value” (V) representations.</li></ol><pre>q = self.wq(x)<br>k = self.wk(x)<br>v = self.wv(x)</pre><p>2. Dividing these tensors into multiple “heads”.</p><pre>q = q.contiguous().view(b, seq_len, self.n_heads, self.head_dim)<br>k = k.contiguous().view(b, seq_len, self.n_heads, self.head_dim)<br>v = v.contiguous().view(b, seq_len, self.n_heads, self.head_dim)</pre><p>3. Computing attention scores via the dot product of Q and K.</p><pre>    attn = torch.matmul(q, k.transpose(2, 3)) / math.sqrt(self.head_dim)</pre><p>4. Applying softmax to these scores to procure attention weights.</p><pre>    attn_scores = F.softmax(attn, dim=-1)</pre><p>5. Multiplying the attention weights with the V tensor, yielding the attended features.</p><pre>    out = torch.matmul(attn_scores, v)</pre><p>6. Aggregating results across all heads and projecting to provide the concluding output.</p><pre>    out = out.contiguous().view(b, seq_len, -1)<br>    return self.wo(out)</pre><h4>Attention Block</h4><p>The AttentionBlock module encapsulates a typical block found within transformer architectures. It primarily consists of two significant components: a multi-head self-attention mechanism and a feed-forward neural network (FFN). Additionally, layer normalization and skip connections (residual connections) are employed to facilitate better learning and gradient flow.</p><p>let’s code the multi-head attention block.</p><pre>class AttentionBlock(nn.Module):<br>    def __init__(self, args: ModelArgs):<br>        super().__init__()<br>        self.layer_norm_1 = nn.LayerNorm(args.dim)<br>        self.attn = MultiHeadAttention(args)<br>        <br>        self.layer_norm_2 = nn.LayerNorm(args.dim)<br>        <br>        self.ffn = nn.Sequential(<br>            nn.Linear(args.dim, args.hidden_dim),<br>            nn.GELU(),<br>            nn.Dropout(args.dropout),<br>            nn.Linear(args.hidden_dim, args.dim),<br>            nn.Dropout(args.dropout)<br>        )<br>    <br>    def forward(self, x):<br>        x = x + self.attn(self.layer_norm_1(x))<br>        x = x + self.ffn(self.layer_norm_2(x))<br>        return x</pre><p>Let’s delve deeper into its structure:</p><ol><li><strong>Layer Normalization (Pre-Attention)</strong> Before feeding the input x into the multi-head attention mechanism, it&#39;s normalized using<strong> nn.LayerNorm.</strong></li></ol><pre>self.layer_norm_1 = nn.LayerNorm(args.dim)<br>x = self.layer_norm_1(x)</pre><p>2. <strong>Multi-Head Self-Attention</strong> This component allows the model to focus on different parts of the input sequence when generating its output.</p><pre>self.attn = MultiHeadAttention(args)<br>x = x + self.attn(x)</pre><p>3. <strong>Layer Normalization (Pre-Feed-Forward Network)</strong> Just like before the multi-head attention mechanism, the output is normalized again using LayerNorm before feeding it into the FFN.</p><pre>self.layer_norm_2 = nn.LayerNorm(args.dim)<br>x = self.layer_norm_2(x)</pre><p>4.<strong> Feed-Forward Neural Network (FFN) </strong>The FFN consists of two linear layers separated by a GELU activation function. There’s also dropout applied for regularization.</p><pre>self.ffn = nn.Sequential(<br>    nn.Linear(args.dim, args.hidden_dim),<br>    nn.GELU(),<br>    nn.Dropout(args.dropout),<br>    nn.Linear(args.hidden_dim, args.dim),<br>    nn.Dropout(args.dropout)<br>)<br>x = x + self.ffn(x)</pre><p>5. Residual Connections Residual or skip connections are vital for deep architectures like transformers. They help in preventing the vanishing gradient problem and helps in model convergence. In the code, these are represented by the addition operations where the input is added back to the output of both the attention mechanism and the FFN.</p><pre>x = x + self.attn(...)<br>x = x + self.ffn(...)</pre><p>By sequentially organizing the operations, this block ensures efficient and effective feature transformation, which is essential for the transformer’s performance.</p><h4>Converting Image into Patches</h4><p>Before creating our full vistion transformer model, we need to create a utility function that transforms images into non-overlapping patches.</p><pre>def img_to_patch(x, patch_size, flatten_channels=True):<br>    # x: Input image tensor <br>    # B: Batch size, C: Channels, H: Height, W: Width<br>    B, C, H, W = x.shape  # (B, C, H, W)<br>    <br>    # Reshape the image tensor to get non-overlapping patches<br>    x = x.reshape(B, C, H//patch_size, patch_size, W//patch_size, patch_size)  # (B, C, H/patch_size, patch_size, W/patch_size, patch_size)<br>    <br>    # Permute to group the patches and channels<br>    x = x.permute(0, 2, 4, 1, 3, 5)  # (B, H/patch_size, W/patch_size, C, patch_size, patch_size)<br>    <br>    # Flatten the height and width dimensions for patches<br>    x = x.flatten(1,2)  # (B, (H/patch_size * W/patch_size), C, patch_size, patch_size)<br>    <br>    # Option to flatten the channel and spatial dimensions<br>    if flatten_channels:<br>        x = x.flatten(2,4)  # (B, (H/patch_size * W/patch_size), (C * patch_size * patch_size))<br>    <br>    return x</pre><p>The img_to_patch function takes an image tensor and converts it into non-overlapping patches of a specified size. This operation is typically used in vision transformers to represent an image as a sequence of flattened patches. The function provides an option to flatten the channels or keep them separate.</p><h4>Vision Transformer</h4><p>The VisionTransformer effectively integrates the previously discussed components to construct the final model. It operates as an encoder-only architecture similar to BERT, where all tokens attend to all other tokens. Moreover, we introduce an additional class token (cls_token) to every sequence in the batch, and this will be utilized later for classification, much like how BERT does with its special [CLS] token.</p><pre>class VisionTransformer(nn.Module):<br>    def __init__(self, args: ModelArgs):<br>        super().__init__()<br><br>        # Define the patch size<br>        self.patch_size = args.patch_size<br>        <br>        # Embedding layer to transform flattened patches to desired dimension<br>        self.input_layer = nn.Linear(args.n_channels * (args.patch_size ** 2), args.dim)<br><br>        # Create the attention blocks for the transformer<br>        attn_blocks = []<br>        for _ in range(args.n_layers):<br>            attn_blocks.append(AttentionBlock(args))<br>        <br>        # Create the transformer by stacking the attention blocks<br>        self.transformer = nn.Sequential(*attn_blocks)<br>        <br>        # Define the classifier<br>        self.mlp = nn.Sequential(<br>            nn.LayerNorm(args.dim),<br>            nn.Linear(args.dim, args.n_classes)<br>        )<br>        <br>        # Dropout layer for regularization<br>        self.dropout = nn.Dropout(args.dropout)<br>        <br>        # Define the class token (similar to BERT&#39;s [CLS] token)<br>        self.cls_token = nn.Parameter(torch.randn(1, 1, args.dim))<br>        <br>        # Positional embeddings to give positional information to transformer<br>        self.pos_embedding = nn.Parameter(torch.randn(1, 1+args.n_patches, args.dim))<br>    <br>    def forward(self, x):<br>        # Convert image to patches and flatten<br>        x = img_to_patch(x, self.patch_size)<br>        b, seq_len, _ = x.shape<br>        <br>        # Transform patches using the embedding layer<br>        x = self.input_layer(x)<br>        <br>        # Add the class token to the beginning of each sequence<br>        cls_token = self.cls_token.repeat(b, 1, 1)<br>        x = torch.cat([cls_token, x], dim=1)<br>        <br>        # Add positional embeddings to the sequence<br>        x = x + self.pos_embedding[:,:seq_len+1]<br>        <br>        # Apply dropout<br>        x = self.dropout(x)<br>        <br>        # Process sequence through the transformer<br>        x = self.transformer(x)<br><br>        # Retrieve the class token&#39;s representation (for classification)<br>        x = x.transpose(0, 1)<br>        cls = x[0]<br><br>        # Classify using the representation of the class token<br>        out = self.mlp(cls)<br>        return out</pre><p>Let’s dive into its key aspects:</p><ol><li>Patch Embedding Instead of operating on raw pixels, the image is divided into fixed-size patches. Each patch is then linearly transformed (flattened and passed through a linear layer) to a specified dimension (args.dim).</li></ol><pre>self.patch_size = args.patch_size<br>self.input_layer = nn.Linear(args.n_channels * (args.patch_size ** 2), args.dim)<br>x = img_to_patch(x, self.patch_size) <br>x = self.input_layer(x)</pre><p>2. Transformer Blocks A sequence of attention blocks to process the embedded patches. The number of blocks is defined by args.n_layers.</p><pre>attn_blocks = []<br>for _ in range(args.n_layers):<br>    attn_blocks.append(AttentionBlock(args))<br>self.transformer = nn.Sequential(*attn_blocks)<br>x = self.transformer(x)</pre><p>3. CLS Token and Position Embeddings A class token is added to the sequence of embedded patches. This token is later used to obtain the final classification output. Positional embeddings are added to provide the transformer with information about the relative positions of patches.</p><pre>self.cls_token = nn.Parameter(torch.randn(1, 1, args.dim))<br>self.pos_embedding = nn.Parameter(torch.randn(1, 1+args.n_patches, args.dim))<br>cls_token = self.cls_token.repeat(b, 1, 1)<br>x = torch.cat([cls_token, x], dim=1)<br>x = x + self.pos_embedding[:,:seq_len+1]</pre><p>4. Dropout is applied for regularization purposes.</p><pre>self.dropout = nn.Dropout(args.dropout)<br>x = self.dropout(x)</pre><p>5. Classifier The classification head. It uses the class token’s[CLS] representation after it’s been processed by all transformer blocks.</p><pre>self.mlp = nn.Sequential(<br>    nn.LayerNorm(args.dim),<br>    nn.Linear(args.dim, args.n_classes)<br>)<br>x = x.transpose(0, 1)<br>cls = x[0]<br>out = self.mlp(cls)</pre><p>The Below code snippet provides a setup for preprocessing and loading the CIFAR10 dataset</p><pre># Path to the directory where CIFAR10 data will be stored/downloaded<br>DATA_DIR = &quot;../data&quot;<br><br># Define the transformation for testing dataset:<br># 1. Convert images to tensors.<br># 2. Normalize the tensors using the mean and standard deviation of CIFAR10 dataset.<br>test_transform = transforms.Compose([<br>    transforms.ToTensor(),<br>    transforms.Normalize([0.49139968, 0.48215841, 0.44653091], [0.24703223, 0.24348513, 0.26158784])<br>])<br><br># Define the transformation for training dataset:<br># 1. Apply random horizontal flip for data augmentation.<br># 2. Perform random resizing and cropping of images for data augmentation.<br># 3. Convert images to tensors.<br># 4. Normalize the tensors using the mean and standard deviation of CIFAR10 dataset.<br>train_transform = transforms.Compose([<br>    transforms.RandomHorizontalFlip(),<br>    transforms.RandomResizedCrop((32, 32), scale=(0.8, 1.0), ratio=(0.9, 1.1)),<br>    transforms.ToTensor(),<br>    transforms.Normalize([0.49139968, 0.48215841, 0.44653091], [0.24703223, 0.24348513, 0.26158784])<br>])<br><br># Load the CIFAR10 training dataset with the defined training transformation.<br># The dataset will be downloaded if not present in the DATA_DIR.<br>train_dataset = CIFAR10(root=DATA_DIR, train=True, transform=train_transform, download=True)<br><br># Load the CIFAR10 testing dataset with the defined testing transformation.<br># The dataset will be downloaded if not present in the DATA_DIR.<br>test_set = CIFAR10(root=DATA_DIR, train=False, transform=test_transform, download=True)<br><br># Split the training dataset into training and validation sets.<br># The training set will have 45000 images, and the validation set will have 5000 images.<br>train_set, val_set = torch.utils.data.random_split(train_dataset, [45000, 5000])</pre><p>Let’s setup the data loaders for training, validation and test datasets</p><pre># Define the batch size for training, validation, and testing.<br>batch_size = 16<br><br># Define the number of subprocesses to use for data loading.<br>num_workers = 4<br><br># Create a DataLoader for the training and validation dataset:<br># 1. Shuffle the training data for each epoch.<br># 2. Drop the last batch if its size is not equal to `batch_size` to maintain consistency.<br>train_loader = torch.utils.data.DataLoader(dataset=train_set, <br>                                           batch_size=batch_size, <br>                                           shuffle=True,<br>                                           num_workers=num_workers, <br>                                           drop_last=True)<br><br># Do not drop any data; process all the validation data.<br>val_loader = torch.utils.data.DataLoader(dataset=val_set, <br>                                         batch_size=batch_size, <br>                                         shuffle=False,<br>                                         num_workers=num_workers, <br>                                         drop_last=False)<br><br># Create a DataLoader for the testing dataset:<br># Do not drop any data; process all the test data.<br>test_loader = torch.utils.data.DataLoader(dataset=test_set, <br>                                          batch_size=batch_size, <br>                                          shuffle=False,<br>                                          num_workers=num_workers, <br>                                          drop_last=False)</pre><p>Let’s configure the model, optimization strategy, and training criterion using cross entropy loss</p><pre># Model, Loss and Optimizer<br>device = &quot;cuda:0&quot; if torch.cuda.is_available() else 0<br>args = ModelArgs()<br>model = VisionTransformer(args).to(device)<br>criterion = nn.CrossEntropyLoss()<br>optimizer = torch.optim.Adam(model.parameters(), lr=3e-4)<br>lr_scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[80, 130], gamma=0.1)</pre><p>Time to bring our model to life! Let’s train it.</p><pre>num_epochs = 150  # example value, adjust as needed<br><br>for epoch in range(num_epochs):<br>    <br>    # Training Phase<br>    model.train()<br>    total_loss = 0.0<br>    for i, (inputs, labels) in enumerate(train_loader):<br>        inputs, labels = inputs.to(device), labels.to(device)<br>        <br>        # Zero the parameter gradients<br>        optimizer.zero_grad()<br>        <br>        # Forward pass<br>        outputs = model(inputs)<br>        loss = criterion(outputs, labels)<br>        <br>        # Backward pass and optimize<br>        loss.backward()<br>        optimizer.step()<br>        <br>        total_loss += loss.item()<br>    <br>    avg_train_loss = total_loss / len(train_loader)<br>    print(f&quot;Epoch [{epoch+1}/{num_epochs}], Training Loss: {avg_train_loss:.4f}&quot;)<br><br>    # Validation Phase<br>    model.eval()<br>    total_val_loss = 0.0<br>    correct = 0<br>    total = 0<br>    with torch.no_grad():<br>        for inputs, labels in val_loader:<br>            inputs, labels = inputs.to(device), labels.to(device)<br><br>            # Forward pass<br>            outputs = model(inputs)<br>            loss = criterion(outputs, labels)<br>            <br>            total_val_loss += loss.item()<br><br>            _, predicted = outputs.max(dim=-1)<br>            total += labels.size(0)<br>            correct += predicted.eq(labels).sum().item()<br><br>    avg_val_loss = total_val_loss / len(val_loader)<br>    val_accuracy = 100 * correct / total<br>    print(f&quot;Epoch [{epoch+1}/{num_epochs}], Validation Loss: {avg_val_loss:.4f}, Validation Accuracy: {val_accuracy:.2f}%&quot;)<br><br>    # Update the learning rate<br>    lr_scheduler.step()<br><br>print(&quot;Training complete!&quot;)</pre><p>Now, let’s evaluate our model’s accuracy on the test set.</p><pre>model.eval()<br>correct = 0<br>total = 0<br>with torch.no_grad():<br>    for inputs, labels in test_loader:  <br>        inputs, labels = inputs.to(device), labels.to(device)<br><br>        # Forward pass<br>        outputs = model(inputs)<br>        _, predicted = outputs.max(dim=-1)<br>        total += labels.size(0)<br>        correct += predicted.eq(labels).sum().item()<br><br>test_accuracy = 100 * correct / total<br>print(f&quot;Test Accuracy: {test_accuracy:.2f}%&quot;)</pre><p>Test Accuracy: 75.07%</p><h4>colab link:-</h4><p><a href="https://colab.research.google.com/github/pashanitw/pytorch_examples/blob/main/transformer/VisionTransformer.ipynb">https://colab.research.google.com/github/pashanitw/pytorch_examples/blob/main/transformer/VisionTransformer.ipynb</a></p><h4>Summary:-</h4><p>Our model’s accuracy is lower than a basic CNN. This is because vision transformers need more data to excel. With the CIFAR-10 dataset, we expect around 75–80% accuracy. But on larger datasets, vision transformers often do better than CNNs.</p><h4>Part-2 Sneak Peek:-</h4><p>Coming up in Part 2: I’ll guide you through the hands-on implementation of LoRa (Low Rank Approximation) tailored for the vision transformer we’ve covered. Stay Tuned!</p><h4><strong>Conclusion:-</strong></h4><p>If you found value in this, connect or reach out to me on <a href="https://www.linkedin.com/in/pasha-shaik/">LinkedIn</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=beef0f3aef5c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Guide to Hand-Calculating FLOPs and MACs]]></title>
            <link>https://medium.com/@pashashaik/a-guide-to-hand-calculating-flops-and-macs-fa5221ce5ccc?source=rss-492b6b477184------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa5221ce5ccc</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[naturallanguageprocessing]]></category>
            <dc:creator><![CDATA[Pasha Shaik]]></dc:creator>
            <pubDate>Wed, 20 Sep 2023 14:47:12 GMT</pubDate>
            <atom:updated>2023-11-06T17:20:25.010Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ssp4jBJYdzeXgsnNbgFyRQ.png" /></figure><h3>Why is Understanding MACs and FLOPs in Neural Networks Important?</h3><p>In this session, we are going to delve deep into the concepts of MACs (Multiply-Accumulate Operations) and FLOPs (Floating Point Operations) within the context of neural networks. By learning how to calculate these manually using pen and paper, you’ll acquire a foundational understanding of the computational complexity and efficiency of various network structures.</p><p>Understanding MACs and FLOPs is not just an academic exercise; it is a critical component in optimizing neural networks for performance and efficiency. It helps in designing models that are both computationally efficient and effective, ultimately saving time and resources during the training and inference phases.</p><p>here is a fully functioning example in a <a href="https://colab.research.google.com/github/pashanitw/pytorch_examples/blob/main/basic/params_flops.ipynb">colab</a> notebook</p><h3>Resource Efficiency:</h3><p>Understanding FLOPs helps in estimating the computational cost of a neural network. By optimizing the number of FLOPs, one can potentially reduce the time taken to train or run a neural network.</p><h3>Memory Efficiency:</h3><p>MAC operations often dictate the memory usage of the network since they are directly related to the number of parameters and activations in the network. Reducing MACs can help in making the network memory efficient.</p><h3>Energy Consumption:</h3><h3>Power Efficiency:</h3><p>Both FLOPs and MAC operations contribute to the power consumption of the hardware on which the neural network is running. By optimizing these metrics, one can potentially reduce the energy requirements of running the network, which is particularly important in mobile and embedded devices.</p><h3>Model Optimization:</h3><h3>Pruning and Quantization:</h3><p>Understanding FLOPs and MACs can assist in optimizing a neural network through techniques like pruning (removing unnecessary connections) and quantization (reducing the precision of weights and activations), which aim to reduce computational and memory costs.</p><h3>Performance Benchmarking:</h3><h3>Comparison between Models:</h3><p>FLOPs and MACs provide a means to compare different models in terms of their computational complexity, which can be a criterion for selecting models for specific applications.</p><h3>Hardware Benchmarking:</h3><p>These metrics can also be used to benchmark the performance of different hardware platforms in running neural networks.</p><h3>Deployment on Edge Devices:</h3><h3>Real-time Applications:</h3><p>For real-time applications, especially on edge devices with limited computational resources, understanding and optimizing these metrics is critical in ensuring that the network can run within the time constraints of the application.</p><h3>Battery Life:</h3><p>In battery-powered devices, reducing the computational cost (and hence energy consumption) of neural networks can help in extending the battery life.</p><h3>Research and Development:</h3><h3>Designing New Algorithm:</h3><p>Researchers can use these metrics as guidelines when developing new algorithms or neural network architectures, aiming to improve computational efficiency without sacrificing accuracy.</p><h3>Step 1: Understand the Definitions</h3><h3>FLOP</h3><p>A FLOP (Floating Point OPeration) is considered to be either an addition, subtraction, multiplication, or division operation.</p><h3>MAC</h3><p>A MAC (Multiply-ACCumulate) operation is essentially a multiplication followed by an addition, i.e., MAC = a * b + c. It counts as two FLOPs (one for multiplication and one for addition).</p><h3>Step 2: Analyze Each Layer</h3><p>calculating the number of floating-point operations or multiply-accumulate operations to understand the computational complexity of each layer.</p><h3>1. Fully Connected Layer (Dense Layer)</h3><p>Now, we will create a simple neural network with 3 layers and begin counting the operations involved. Here is the formula for calculating the operations in the first linear layer, which is a fully connected (or dense) layer:</p><p>For a fully connected layer with I inputs and O outputs, the number of operations are as follows:</p><ul><li><strong>MACs</strong>: I × O</li><li><strong>FLOPs</strong>: 2 × (I × O) (since each MAC counts as two FLOPs)</li></ul><pre>class SimpleLinearModel(nn.Module):<br>    def __init__(self):<br>        super(SimpleLinearModel,self).__init__()<br>        self.fc1 = nn.Linear(in_features=10, out_features=20, bias=False)<br>        self.fc2 = nn.Linear(in_features=20, out_features=15, bias=False)<br>        self.fc3 = nn.Linear(in_features=15, out_features=1, bias=False)<br>    def forward(self, x):<br>        x = self.fc1(x)<br>        x = F.relu(x)<br>        x = self.fc2(x)<br>        F.relu(x)<br>        x = self.fc3(x)<br>        return x<br><br>linear_model = SimpleLinearModel().cuda()<br>sample_data = torch.randn(1, 10).cuda()</pre><p><strong>step 1: Identifying Layer Parameters</strong> For the given model, we have three linear layers defined as:</p><ul><li>fc1: 10 input features, 20 output features</li><li>fc2: 20 input features, 15 output features</li><li>fc3: 15 input features, 1 output feature</li></ul><p><strong>Step 2: Calculating FLOPs and MACs</strong> Now, calculate MACs and FLOPs for each layer:</p><blockquote>Layer fc1:</blockquote><blockquote>MACs = 10 × 20 = 200</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 200 = 400</blockquote><blockquote>Layer fc2:</blockquote><blockquote>MACs = 20 × 15 = 300</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 300 = 600</blockquote><blockquote>Layer fc3:</blockquote><blockquote>MACs = 15 × 1 = 15</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 15 = 30</blockquote><p><strong>Step 3: Summing Up the Results</strong> Finally, to find the total number of MACs and FLOPs for a single input passing through the entire network, we sum the results from all layers:</p><ul><li>Total MACs = MACs(fc1) + MACs(fc2) + MACs(fc3) = 200 + 300 + 15 = 515</li><li>Total FLOPs = FLOPs(fc1) + FLOPs(fc2) + FLOPs(fc3) = 400 + 600 + 30 = 1030</li></ul><h3>Verifying FLOPs and MACs with torchprofile Library</h3><p>You can use the torchprofile library to verify the FLOPs and MACs calculations for the given neural network model. Here&#39;s how to do it:</p><pre>macs = profile_macs(linear_model, sample_data)<br>print(macs)<br><br>#515</pre><h3>2. Convolutional Neural Networks(CNNs)</h3><p>Now, let’s determine the MACs (Multiply-Accumulates) and FLOPs (Floating-Point Operations) for a straightforward convolutional model. This calculation can be a bit more involved than our previous example with dense layers, mainly due to factors like stride, padding, and kernel size. However, I’ll break it down to make it easier for our learning purpose.</p><pre>class SimpleConv(nn.Module):<br>    def __init__(self):<br>        super(SimpleConv, self).__init__()<br>        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)<br>        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)<br>        self.fc =  nn.Linear(in_features=32*28*28, out_features=10)<br>    <br>    def forward(self, x):<br>        x = self.conv1(x)<br>        x = F.relu(x)<br>        x = self.conv2(x)<br>        x = F.relu(x)<br>        x = x.view(x.shape[0], -1)<br>        x = self.fc(x)<br>        return x<br><br>x = torch.rand(1, 1, 28, 28).cuda()<br>conv_model = SimpleConv().cuda()</pre><h3>Important Consideration for Calculating Convolutional Operations</h3><ul><li>When calculating operations for convolutional kernels, it’s crucial to remember that the number of channels in the kernel should match the number of channels in the input. For instance, if our input is an RGB image with three color channels, the kernel’s dimensions will be 3x3x3 to account for the input&#39;s three channels.</li><li>For the purpose of our demonstration, we’ll maintain a consistent image size throughout the convolutional layers. To achieve this, we’ll set both the padding and stride values to 1.</li></ul><p><strong>step 1: Identifying Layer Parameters</strong></p><p>For the given model, we have two conv layers and one linear layer defined as:</p><ul><li>conv1: 1 input channels, 16 output channels, kernel size is 3</li><li>conv2: 16 input channels, 32 output channels</li><li>fc: 32*28*28 input features, 1 output feature. because our image is not changed in the convolutional layers</li></ul><p><strong>Step 2: Calculating FLOPs and MACs</strong> Now, calculate MACs and FLOPs for each layer:</p><p>formula is output_image_size * kernel shape * output_channels</p><blockquote>Layer conv1:</blockquote><blockquote>MACs = 28 * 28 * 3 * 3 * 1 * 16 = 1,12,896</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 200 = 2,25,792</blockquote><blockquote>Layer conv2:</blockquote><blockquote>MACs = 28 × 28 * 3 * 3 * 16 * 32 = 3,612,672</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 300 = 600 = 7,225,344</blockquote><blockquote>Layer fc:</blockquote><blockquote>MACs = 32 * 28 * 28 * 10 = 250,880</blockquote><blockquote>FLOPs = 2 × MACs = 2 × 15 = 501,760</blockquote><p><strong>Step 3: Summing Up the Results</strong> Finally, to find the total number of MACs and FLOPs for a single input passing through the entire network, we sum the results from all layers:</p><ul><li>Total MACs = MACs(conv1) + MACs(conv2) + MACs(fc) = 1,12,896 + 3,612,672 + 250,880 = 39,76,448</li><li>Total FLOPs = FLOPs(fc1) + FLOPs(fc2) + FLOPs(fc3) = 2,25,792 + 7,225,344 + 501,760 = 7,952,896</li></ul><h3>Verifying Operations with torchprofile Library</h3><pre>macs = profile_macs(conv_model,(x,))<br>print(macs)<br><br>#3976448</pre><h3>3. Self-Attention Block</h3><p>Having covered MACs for linear and convolutional layers, our next step is to determine the FLOPs (Floating-Point Operations) for a Self-Attention block, a crucial component in large language models. This calculation is essential for understanding the computational complexity of such models. Let’s delve into it.</p><pre>class SimpleAttentionBlock(nn.Module):<br>    def __init__(self, embed_size, heads):<br>        super(SimpleAttentionBlock, self).__init__()<br>        self.embed_size = embed_size<br>        self.heads = heads<br>        self.head_dim = embed_size // heads<br><br>        assert (<br>            self.head_dim * heads == embed_size<br>        ), &quot;Embedding size needs to be divisible by heads&quot;<br><br>        self.values = nn.Linear(self.embed_size, self.embed_size, bias=False)<br>        self.keys = nn.Linear(self.embed_size, self.embed_size, bias=False)<br>        self.queries = nn.Linear(self.embed_size, self.embed_size, bias=False)<br>        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)<br><br>    def forward(self, values, keys, queries, mask):<br>        N = queries.shape[0]<br>        value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1]<br>        print(values.shape)<br>        values = self.values(values).reshape(N,  self.heads, value_len, self.head_dim)<br>        keys = self.keys(keys).reshape(N, self.heads, key_len, self.head_dim)<br>        queries = self.queries(queries).reshape(N,  self.heads, query_len, self.head_dim)<br><br><br>        energy = torch.matmul(queries, keys.transpose(-2, -1))        <br><br>        if mask is not None:<br>            energy = energy.masked_fill(mask == 0, float(&quot;-1e20&quot;))<br><br>        attention = torch.nn.functional.softmax(energy, dim=3)<br>        out = torch.matmul(attention, values).reshape(<br>            N, query_len, self.heads * self.head_dim<br>        )<br><br>        return self.fc_out(out)</pre><p><strong>step 1: Identifying Layer Parameters</strong></p><h3>Linear Transformations</h3><p><strong>let’s define hyper_params</strong></p><ul><li>batch_size = 1</li><li>seq_len = 10</li><li>embed_size = 256</li></ul><p>In the attention block, we have three linear transformations (for queries, keys, and values), and one at the end (fc_out).</p><ul><li>Input Size: [batch_size, seq_len, embed_size]</li><li>Linear transformation matrix: [embed_size, embed_size]</li><li>MACs: batch_size×seq_len×embed_size×embed_size</li></ul><p>Query, Key, Value linear transformation:</p><blockquote>MACs for Query Transformation = 1 * 10 * 256 * 256 = 6,55,360</blockquote><blockquote>MACs for Key Transformation = 1 * 10 * 256 * 256 = 6,55,360</blockquote><blockquote>MACS for Value Transformation = 1 * 10 * 256 * 256 = 6,55,360</blockquote><p>Energy Calculation Calculation: queries (reshaped) dot keys (reshaped) — a dot product operation.</p><blockquote>Macs: batch_size×seq_len×seq_len×heads×head_dim</blockquote><blockquote>query and key dot product</blockquote><blockquote>MACS = 1 * 10 * 10 * 32 [32 because 256/8 divide by heads] = 25,600</blockquote><p>Output from Attention Weights and Values Calculation: attention weights dot values (reshaped) — another dot product operation.</p><blockquote>Macs : batch_size×seq_len×seq_len×heads×head_dim</blockquote><blockquote>attention and value dot product</blockquote><blockquote>Macs = 1 * 10 * 10 * 32 = 25,600</blockquote><blockquote>Fully Connected Output (fc_out)</blockquote><blockquote>Macs: batch_size×seq_len×heads×head_dim×embed_size</blockquote><blockquote>Macs = 1 * 10 * 8 * 32 * 256 = 6,55,360</blockquote><p><strong>Step 2: Summing Up the Results</strong></p><ul><li>Total MACs = MACs(conv1) + MACs(conv2) + MACs(fc) = 6,55,360 + 6,55,360 + 6,55,360 + 25,600 + 25,600 + 6,55,360 = 26,72,640</li><li>Total FLOPs = 2 * Total MACs = 53,45,280</li></ul><h3>Verifying Operations with torchprofile Library</h3><pre># Create an instance of the model<br>model = SimpleAttentionBlock(embed_size=256, heads=8).cuda()<br><br># Generate some sample data (batch of 5 sequences, each of length 10, embedding size 256)<br>values = torch.randn(1, 10, 256).cuda()<br>keys = torch.randn(1, 10, 256).cuda()<br>queries = torch.randn(1, 10, 256).cuda()<br><br># No mask for simplicity<br>mask = None<br># Forward pass with the sample data<br>macs = profile_macs(model, (values, keys, queries, mask))<br>print(macs)<br><br>#2672640</pre><h3>Summary: Scaling MACs and FLOPs for Different Batch Sizes</h3><p>Throughout our calculations, we’ve primarily considered a batch size of 1. However, it’s important to note that scaling MACs and FLOPs for larger batch sizes is straightforward.</p><p>To compute MACs or FLOPs for a batch size greater than one, you can simply multiply the total MACs or FLOPs obtained for batch size 1 by the desired batch size value. This scaling allows you to estimate computational requirements for various batch sizes in your neural network models.</p><p>Keep in mind that the results will directly scale linearly with the batch size. For instance, if you have a batch size of 32, you can obtain the MACs or FLOPs by multiplying the values for batch size 1 by 32.</p><h4>Conclusion</h4><p>I hope you found this article insightful and valuable. If you enjoyed the content and wish to stay updated with future posts, please consider following me on <a href="https://www.linkedin.com/in/pasha-shaik/">LinkedIn</a>. Your support motivates me to continue sharing knowledge. Thank you for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa5221ce5ccc" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>