Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
146750 stories
·
33 followers

Build a Full-Featured PDF Editor in Angular for Modern Web Apps

1 Share

How to Build a PDF Editor in Angular with Viewing and Editing Features

TL;DR: Learn how to turn Syncfusion’s Angular PDF Viewer into a full PDF Editor with tools for annotation, form filling, digital signatures, page manipulation, and toolbar customization. This guide walks you through setup, integration, and advanced editing capabilities to deliver a powerful PDF workflow directly in your Angular application.

Ever thought about editing PDFs directly inside your Angular app, without relying on bulky desktop tools? You’re definitely not alone. As more teams move toward digital‑first workflows, the demand for smooth, in‑browser PDF editing has grown rapidly.

These days, simply viewing a PDF isn’t enough. Users expect to interact with documents: highlight text, add comments, fill out forms, rearrange pages, and even sign documents, all without leaving the browser. To meet those expectations, you need more than a basic PDF viewer. You need a solution that combines powerful viewing and editing features.

That’s exactly where Syncfusion® Angular PDF Viewer fits in. It goes far beyond rendering PDFs by offering built‑in tools for annotations, form fields, signatures, and page organization. In other words, it turns a standard viewer into a complete Angular PDF Editor, without complex setup or heavy coding.

In this blog, we’ll walk through how to build a feature‑rich PDF Editor in Angular using a clean, developer‑friendly approach.

Let’s get started!

Experience a leap in PDF technology with Syncfusion's PDF Library, shaping the future of digital document processing.

Set up your Angular PDF Viewer

Getting started with Syncfusion’s Angular PDF Viewer is straightforward. In the next few steps, we will go from creating a new Angular app to loading a PDF in the browser, with editing tools ready to go.

Step 1: Create an Angular application

First, create a new Angular app using the Angular CLI, which simplifies both project creation and configuration. Run the following commands in your terminal:

# Install Angular CLI globally
npm install -g @angular/cli

# Create a new Angular application and navigate into it
ng new my-app
cd my-app

Step 2: Install the Angular PDF Viewer package

After your Angular project is set up, add the Syncfusion Angular PDF Viewer. This component serves as the foundation for the PDF Editor app, providing all the essential features for viewing and editing PDFs directly within your Angular app.

To install the PDF Viewer, navigate to your project directory and run the following command:

npm install @syncfusion/ej2-angular-pdfviewer --save

Step 3: Adding CSS reference

To ensure our PDF Viewer looks professional and functions correctly, we need to include the required Syncfusion CSS files in the src/styles.css file. These styles provide layout, themes, and visual contrast for all PDF Viewer components.

@import '../node_modules/@syncfusion/ej2-base/styles/material.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/material.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material.css';
@import '../node_modules/@syncfusion/ej2-splitbuttons/styles/material.css';
@import '../node_modules/@syncfusion/ej2-pdfviewer/styles/material.css';
@import '../node_modules/@syncfusion/ej2-notifications/styles/material.css';

Step 4: Integrating the Angular PDF Viewer component

Now, render the PDF Viewer in the Angular app. First, register the Syncfusion PDF Viewer module and add its component to the template. This ensures the viewer is fully integrated and ready to display PDFs with all its built-in viewing and editing features.

import { Component, OnInit } from '@angular/core';
import { 
  PdfViewerModule, LinkAnnotationService, BookmarkViewService,
  MagnificationService, ThumbnailViewService, ToolbarService,
  NavigationService, TextSearchService, TextSelectionService,
  PrintService, FormDesignerService, FormFieldsService,
  AnnotationService, PageOrganizerService
} from '@syncfusion/ej2-angular-pdfviewer';

@Component({
  selector: 'app-root',
  standalone: true,
  //declaration of ej2-angular-pdfviewer module into imports
  imports: [ PdfViewerModule],
  // specifies the template string for the PDF Viewer component
  template: `<div class="content-wrapper">
                <ejs-pdfviewer id="pdfViewer"
                    [documentPath]='document'
                    [resourceUrl]='resource'
                    style="height:640px;display:block">
                </ejs-pdfviewer>
             </div>`,
  providers: [LinkAnnotationService, BookmarkViewService, MagnificationService,
    ThumbnailViewService, ToolbarService, NavigationService,
    TextSearchService, TextSelectionService, PrintService,
    AnnotationService, FormDesignerService, FormFieldsService, PageOrganizerService]
})
export class AppComponent implements OnInit {
  public document: string = 'https://cdn.syncfusion.com/content/pdf/pdf-succinctly.pdf';
  public resource: string = "https://cdn.syncfusion.com/ej2/31.2.2./dist/ej2-pdfviewer-lib";
  ngOnInit(): void {
  }
}

Step 5: Run the application

Use the command ng serve --open to start the Angular app. Once compiled, the app will launch at http://localhost:4200.

Here’s what the basic PDF Viewer looks like.

PDF Viewer loaded with a sample document
Angular PDF Viewer loaded with a sample document

Note: For more detailed guidance on installing the Angular PDF Viewer, check out our official documentation.

Explore the wide array of rich features in Syncfusion's PDF Library through step-by-step instructions and best practices.

Customizing the toolbar in Angular PDF Viewer

Toolbar customization is key to building flexible PDF editors. With Angular PDF Viewer, you can remove unused buttons, add custom actions, and organize tools to match your app’s workflow, delivering a tailored user experience.

Build a customized toolbar

With Syncfusion Angular PDF Viewer, the default toolbar can be replaced with a fully customized version tailored to specific workflows. Developers can disable the built-in toolbar using the enableToolbar property or the showToolbar method. Then, introduce custom icons, grouped actions, and specialized tools for tasks such as PDF annotation, signing, or form field editing.

Beyond functionality, the Angular PDF Viewer also enhances the visual experience with built-in themes like Material, Bootstrap, and Fluent. These themes instantly style the toolbar, ensuring a consistent and professional look across apps. For deeper customization, the layout and styling can be refined to deliver a truly unique interface.

PDF Viewer with Custom Toolbar
PDF Viewer with custom toolbar

Note: For more insights, refer to the toolbar customization in the Angular PDF Viewer documentation.

PDF annotation tools for advanced document editing

PDF annotations turn static documents into interactive experiences, making them an essential feature in any modern Angular PDF Viewer. With Syncfusion’s Angular PDF Viewer, users can easily highlight text, underline key points, draw shapes, add sticky notes, use ink, and more without changing the original content. These tools allow users to edit PDFs directly within your Angular PDF Editor application.

Adding annotations is simple: select the Annotation button in the toolbar and choose the desired option, as demonstrated visually below.

Adding annotations via Toolbar
Adding annotations via Toolbar

Adding PDF annotations programmatically

Syncfusion’s Angular PDF Viewer goes beyond UI‑based tools by giving developers complete control through its API. With just a few lines of code, annotations can be added, updated, or removed, making it easy to automate workflows or connect with backend systems.

The code example below demonstrates the basics of adding annotations programmatically.

var pdfviewer = (<any>document.getElementById('pdfViewer')).ej2_instances[0];

pdfviewer.annotation.addAnnotation("Highlight", {
    bounds: [{ x: 97, y: 110, width: 350, height: 14 }],
    pageNumber: 3
} as HighlightSettings);

pdfviewer.annotation.addAnnotation("Underline", {
    bounds: [{ x: 250, y: 148, width: 345, height: 14 }],
    pageNumber: 3
} as HighlightSettings);

Note: For more advanced examples and features, check the official documentation.

Witness the advanced capabilities of Syncfusion's PDF Library with feature showcases.

Adding and managing form fields for editing PDFs

Form fields transform a fixed PDF into an interactive document. With Syncfusion’s Angular PDF Viewer, users can fill out forms, capture data, and create fields like text boxes, checkboxes, and radio buttons, all powered by the built-in Form Designer. This makes adding and editing PDF form fields quick and effortless for real-world workflows such as contracts, applications, or surveys.

Adding fields is simple: open the Form Designer from the toolbar, select the field type, and drag it into place, as shown in the visual below.

Adding and managing form fields for PDF editing
Adding and managing form fields for PDF editing

Note: For more insights about form designers, check out our official documentation.

Managing form fields programmatically

With Syncfusion Angular PDF Viewer’s flexible API, you can dynamically add fields, update properties, or remove them based on user actions, perfect for automated workflows and advanced customization.

The code examples below show the basics of adding, updating, and deleting form fields.

this.pdfviewerControl?.formDesignerModule.addFormField("Textbox", {
    name: 'First Name',
    bounds: { X: 146, Y: 229, Width: 150, Height: 24 },
} as TextFieldSettings);

this.pdfviewerControl?.formDesignerModule.addFormField("Textbox", {
    name: "Middle Name",
    bounds: { X: 338, Y: 229, Width: 150, Height: 24 },
} as TextFieldSettings);

this.pdfviewerControl?.formDesignerModule.updateFormField(
    this.pdfviewerControl?.formFieldCollections[0],
    { backgroundColor: 'red' } as TextFieldSettings
);

this.pdfviewerControl?.formDesignerModule.deleteFormField(
    this.pdfviewerControl.formFieldCollections[0]
);

Note: For details about toolbar Customization, check our official documentation.

Simply sign PDFs instantly: Simplify your PDF signing experience

Signatures turn a PDF into more than just a text document; they make it official. Whether you’re approving a proposal, signing a contract, or confirming an agreement, adding a signature is essential for trust and compliance. With Syncfusion’s Angular PDF Viewer, signing is fast, easy, and secure. Users can draw or type their signature, upload an image, or fill signature fields directly in the PDF using the built-in UI.

If you’re looking to add a signature to a PDF programmatically, Syncfusion also provides robust APIs that enable seamless automation, making it ideal for integrating smooth workflows.

Here’s a quick demo of the feature in action:

Adding a handwritten signature using Angular PDF Viewer
Adding a handwritten signature using Angular PDF Viewer

Role of signatures in modern PDF workflows

Digital signatures bring authenticity and integrity to PDF documents, ensuring they remain tamper-proof and legally valid. They play a critical role in compliance and secure workflows, making them indispensable for contracts, approvals, and sensitive agreements. With Syncfusion’s Angular PDF Viewer, users can seamlessly view digitally signed PDFs within the application, providing transparency and confidence in every document.

Digital signatures using Syncfusion PDF Viewer
Digital signatures using Syncfusion PDF Viewer

Note: To explore all options, check the official Digital Signature documentation.

Organize PDF pages: Insert, remove, and reorder with ease

Keeping PDFs organized is simple with Syncfusion PDF Viewer. You can insert new pages, remove unwanted ones, reorder, or rotate for perfect alignment, all in just a few clicks. These tools give you complete control over document structure, helping users maintain professional, well-organized documents effortlessly.

To get started, open the Organize PDF tool in the sidebar. A dialog appears where you can add, remove, or reorder pages, then save and view the updated document.

Here’s a quick demo of the feature in action:

Organize Pages in PDF Viewer
Organize Pages in PDF Viewer

Note: For detailed guidance on page management and toolbar customization, check the official overview documentation.

Frequently Asked Questions

What formats are supported for exporting annotations?

Syncfusion PDF Viewer supports exporting annotations in three formats: XFDF (a standards‑based format used across PDF workflows), JSON (a lightweight format ideal for database storage), and a JavaScript object (retrieved using the exportAnnotationsAsObject() method for direct programmatic access).

Can I stop users from editing certain form fields?

Absolutely. You can lock fields or mark them as read‑only. This is useful when you want certain fields to remain visible but not editable.

Can I enable annotations only for specific roles?

Yes. You can control which toolbar tools and annotation actions are available based on the user’s role. You can check it out to learn more.

Can I save or download edited PDFs?

Yes. After editing, whether annotating, signing, or reorganizing pages, users can save the updated PDF locally or upload it to the server for further processing.

Does Syncfusion PDF Viewer support mobile responsiveness?

Yes. The viewer adapts seamlessly to mobile devices and supports touch interactions for annotations, zooming, panning, and signatures.

Which Angular versions are supported?

Syncfusion PDF Viewer supports modern Angular versions (Angular 12 and above).

Can I customize the context menu in the Syncfusion Angular PDF Viewer?

Yes. The Syncfusion Angular PDF Viewer allows full customization of the context menu. You can add, remove, or modify context menu items using the viewer’s customization APIs.

Syncfusion’s high-performance PDF Library allows you to create PDF documents from scratch without Adobe dependencies.

Conclusion

Thank you for exploring how to build a complete PDF Editor with Syncfusion’s Angular PDF Viewer. Creating a powerful editor doesn’t have to be complex; beyond simple viewing, you can enable annotations, form fields, page organization, and digital signatures, all ready to use out of the box. With customization options and robust APIs, the experience can be tailored perfectly to your workflow.

Ready to see the full capabilities of Syncfusion’s Angular PDF Viewer in action? Check out the official demo and documentation.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us through our support forumsupport portal, or feedback portal for queries. We are always happy to assist you!

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Podcast: Developers Can Improve the ESG Aspects of Software By Tackling Early Ethical Debt

1 Share

Erica Pisani, host of the Performance and Sustainability track at QCon London 2025, reflects on lessons from assembling the track and from attending the talks. She touches on the importance of the environmental and social aspects of software and hints at how developers can improve them through small steps in the architecture and practices of software development.

By Erica Pisani
Read the whole story
alvinashcraft
12 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

TLS 1.3 in SQL Server 2025: What It Really Changes for Security

1 Share

Why TLS 1.3 matters 

TLS (Transport Layer Security) is the protocol that encrypts traffic between clients and servers.

For many years, most SQL Server environments have relied on TLS 1.2, which dates back to 2008.

TLS 1.3, finalized in 2018, is a significant evolution. It brings three main benefits: 

Better performance 

With fewer roundtrips and faster negotiation, secure connections are established much more quickly in practice 

Stronger security 

  • Old and weak algorithms are removed (RC4, SHA1, MD5, DES, 3DES…)
  • Perfect Forward Secrecy (PFS) is mandatory. Even if a private key is compromised later, past sessions remain protected
  • Simpler cipher suites: less configuration complexity, fewer chances to misconfigure 

Easier compliance 

  • Recommended by NIST
  • Strongly encouraged by PCIDSS 4.0
  • Fewer exceptions to justify during audits 

What SQL Server 2025 adds 

 SQL Server 2022 introduced TLS 1.3 through the new TDS 8.0 (Tabular Data Stream) protocol. And SQL Server 2025 makes it broadly usable across the SQL Server ecosystem, this is not limited to client connections only. 

Components that can use TLS 1.3 

  • Client connections (ODBC, OLE DB, .NET)
  • SQL Server Agent
  • bcp utility
  • sqlcmd utility
  • Always On Availability Groups
  • Always On Failover Cluster Instances
  • Log Shipping
  • Transactional replication
  • Merge replication
  • Peer-to-peer replication
  • Snapshot replication
  • Linked Servers
  • PolyBase
  • SQL Writer Service 

Prerequisites 

Valid certificates on all endpoints

  • Selfsigned certificates are acceptable for testing 
  • In production, use certificates from a trusted CA or an internal PKI 

Updated drivers

  • ODBC Driver 18+ for SQL Server 
  • OLE DB Driver 19+ for SQL Server  
  • Microsoft.Data.SqlClient 5.0+ 
  • Older drivers will either fall back to older TLS versions or fail to connect 

SQL Server configuration  

  • You can enforce encryption on all connections (Force Encryption)
  • You can configure TDS 8.0 as the minimum protocol version (Force Strict Encryption) 

How Copilot can help 

I'm setting up log shipping between two SQL Server 2025 instances with TLS 1.3 enabled. What certificate requirements should I verify ?

 The MSSQL extension with GitHub Copilot can assist you when planning and rolling out TLS 1.3.

Auditing the current state 

Analyze my current SQL Server encryption configuration. What TLS version are my connections using ? Are there any connections using deprecated protocols ?

 Copilot can generate and explain the queries that inspect the relevant DMVs and  interpret the results.

Generating configuration scripts 

Generate a script to configure SQL Server 2025 to require TLS 1.3 for all incoming connections, including the certificate configuration steps.

Diagnosing compatibility issues 

My application fails to connect after enabling TDS 8.0. The error mentions "SSL Provider". What should I check and how do I fix it ?

  Reviewing linked servers 

Review my linked server configurations and identify which onesmight have issues with TLS 1.3 requirements.

Documenting the changes 

Generate documentation for our security team explainingthe TLS configuration changes we're implementing for PCI-DSS compliance.

 

It is possible that the AI generated content is incorrect. You remain responsible for reviewing, validating, and approving it before any use. Do not rely on this output without thorough human verification. Not intended for production use. 

Things to watch out for 

 Linked Servers to legacy systems 

You may have linked servers that point to: 

  • Older SQL Server versions (2016 and earlier)
  • ODBC sources that do not support TLS 1.2/1.3
  • Thirdparty servers with outdated TLS stacks 

If you enforce TLS 1.3 everywhere, these connections can fail.  

Possible approaches: 

  • Upgrade the remote systems
  • Use temporary exceptions (not recommended longterm in production)
  • Introduce a proxy or gateway that can handle protocol translation 

Replication to older versions 

Transactional or merge replication to subscribers running SQL Server 2019 or earlier may require mixed configurations. 

 Legacy applications 

Applications using: 

  • .NET Framework with older System.Data.SqlClient versions
  • Old ODBC/OLE DB drivers
  • Thirdparty tools that are not updated may need to be upgraded or reconfigured before you enforce TLS 1.3. 

Suggested deployment strategy 

These steps are indicative; adapt them to your environment and change management process. 

Phase 1: Audit 

  • Inventory all inbound and outbound connections
  • Identify client types and driver versions
  • Document linked servers and replication paths 

Phase 2: Testing 

  • Enable TLS 1.3 in a staging or preproduction environment
  • Test all critical applications and administrative tools
  • Identify and resolve compatibility issues 

Phase 3: Progressive rollout 

  • Enable TLS 1.3 as an option without forcing it
  • Monitor which connections still negotiate older TLS versions
  • Once all key systems are compatible, enforce TLS 1.3 

References

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Command Palette Dock is an astonishing proposal for a new PowerToys utility

1 Share
We absolutely love PowerToys here at BetaNews, and we are far from being alone. Microsoft’s collection of utilities for Windows has legions of fans who eagerly await new releases and updates. With an incredibly busy team of developers working on the project, PowerToys is a fast-moving piece of software that evolves at a greater pace than most. There are many popular modules included in the PowerToys utility suite, with Command Palette being one of the most loved. And if a recent proposal on GitHub moves forward, it is about to get a serious enhancement. One of PowerToys’ key developers, Niels… [Continue Reading]
Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

More memory-driven price rises

1 Share

Two months ago, we announced increases to the prices of some Raspberry Pi 4 and 5 products. These were driven by an unprecedented rise in the cost of LPDDR4 memory, thanks to competition for memory fab capacity from the AI infrastructure roll-out.

Price rises have accelerated as we enter 2026, and the cost of some parts has more than doubled over the last quarter. As a result, we now need to make further increases to our own pricing, affecting all Raspberry Pi 4 and 5, and Compute Module 4 and 5, products that have 2GB or more of memory.

Memory densityPrice increase
1GB
2GB$10
4GB$15
8GB$30
16GB$60

Raspberry Pi 500 and 500+ are affected, but not Raspberry Pi 400, which remains our lowest-cost all-in-one PC at $60. We have also been able to protect the pricing of 1GB products, including the $35 1GB Raspberry Pi 4 variant, and the $45 1GB Raspberry Pi 5 variant that we launched in December.

We don’t anticipate any changes to the price of Raspberry Pi Zero, Raspberry Pi 3, and other older products, as we currently hold several years’ inventory of the LPDDR2 memory that they use.

Looking ahead

2026 looks likely to be another challenging year for memory pricing, but we are working hard to limit the impact. We’ve said it before, but we’ll say it again: the current situation is ultimately a temporary one, and we look forward to unwinding these price increases once it abates.

The post More memory-driven price rises appeared first on Raspberry Pi.

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Benchmarking Local AI Models

1 Share

Introduction

Selecting the right AI model for your application requires more than reading benchmark leaderboards. Published benchmarks measure academic capabilities, question answering, reasoning, coding, but your application has specific requirements: latency budgets, hardware constraints, quality thresholds. How do you know if Phi-4 provides acceptable quality for your document summarization use case? Will Qwen2.5-0.5B meet your 100ms response time requirement? Does your edge device have sufficient memory for Phi-3.5 Mini?

The answer lies in empirical testing: running actual models on your hardware with your workload patterns. This article demonstrates building a comprehensive model benchmarking platform using FLPerformance, Node.js, React, and Microsoft Foundry Local. You'll learn how to implement scientific performance measurement, design meaningful benchmark suites, visualize multi-dimensional comparisons, and make data-driven model selection decisions.

Whether you're evaluating models for production deployment, optimizing inference costs, or validating hardware specifications, this platform provides the tools for rigorous performance analysis.

Why Model Benchmarking Requires Purpose-Built Tools

You cannot assess model performance by running a few manual tests and noting the results. Scientific benchmarking demands controlled conditions, statistically significant sample sizes, multi-dimensional metrics, and reproducible methodology. Understand why purpose-built tooling is essential.

Performance is multi-dimensional. A model might excel at throughput (tokens per second) but suffer at latency (time to first token). Another might generate high-quality outputs slowly. Your application might prioritize consistency over average performance, a model with variable response times (high p95/p99 latency) creates poor user experiences even if averages look good. Measuring all dimensions simultaneously enables informed tradeoffs.

Hardware matters enormously. Benchmark results from NVIDIA A100 GPUs don't predict performance on consumer laptops. NPU acceleration changes the picture again. Memory constraints affect which models can even load. Test on your actual deployment hardware or comparable specifications to get actionable results.

Concurrency reveals bottlenecks. A model handling one request excellently might struggle with ten concurrent requests. Real applications experience variable load, measuring only single-threaded performance misses critical scalability constraints. Controlled concurrency testing reveals these limits.

Statistical rigor prevents false conclusions. Running a prompt once and noting the response time tells you nothing about performance distribution. Was this result typical? An outlier? You need dozens or hundreds of trials to establish p50/p95/p99 percentiles, understand variance, and detect stability issues.

Comparison requires controlled experiments. Different prompts, different times of day, different system loads, all introduce confounding variables. Scientific comparison runs identical workloads across models sequentially, controlling for external factors.

Architecture: Three-Layer Performance Testing Platform

FLPerformance implements a clean separation between orchestration, measurement, and presentation:

The frontend React application provides model management, benchmark configuration, test execution, and results visualization. Users add models from the Foundry Local catalog, configure benchmark parameters (iterations, concurrency, timeout values), launch test runs, and view real-time progress. The results dashboard displays comparison tables, latency distribution charts, throughput graphs, and "best model for..." recommendations.

The backend Node.js/Express server orchestrates tests and captures metrics. It manages the single Foundry Local service instance, loads/unloads models as needed, executes benchmark suites with controlled concurrency, measures comprehensive metrics (TTFT, TPOT, total latency, throughput, error rates), and persists results to JSON storage. WebSocket connections provide real-time progress updates during long benchmark runs.

Foundry Local SDK integration uses the official foundry-local-sdk npm package. The SDK manages service lifecycle, starting, stopping, health checkin, and handles model operations, downloading, loading into memory, unloading. It provides OpenAI-compatible inference APIs for consistent request formatting across models.

The architecture supports simultaneous testing of multiple models by loading them one at a time, running identical benchmarks, and aggregating results for comparison:

User Initiates Benchmark Run
    ↓
Backend receives {models: [...], suite: "default", iterations: 10}
    ↓
For each model:
    1. Load model into Foundry Local
    2. Execute benchmark suite
       - For each prompt in suite:
         * Run N iterations
         * Measure TTFT, TPOT, total time
         * Track errors and timeouts
         * Calculate tokens/second
    3. Aggregate statistics (mean, p50, p95, p99)
    4. Unload model
    ↓
Store results with metadata
    ↓
Return comparison data to frontend
    ↓
Visualize performance metrics

Implementing Scientific Measurement Infrastructure

Accurate performance measurement requires instrumentation that captures multiple dimensions without introducing measurement overhead:

// src/server/benchmark.js
import { performance } from 'perf_hooks';

export class BenchmarkExecutor {
  constructor(foundryClient, options = {}) {
    this.client = foundryClient;
    this.options = {
      iterations: options.iterations || 10,
      concurrency: options.concurrency || 1,
      timeout_ms: options.timeout_ms || 30000,
      warmup_iterations: options.warmup_iterations || 2
    };
  }
  
  async runBenchmarkSuite(modelId, prompts) {
    const results = [];
    
    // Warmup phase (exclude from results)
    console.log(`Running ${this.options.warmup_iterations} warmup iterations...`);
    for (let i = 0; i < this.options.warmup_iterations; i++) {
      await this.executePrompt(modelId, prompts[0].text);
    }
    
    // Actual benchmark runs
    for (const prompt of prompts) {
      console.log(`Benchmarking prompt: ${prompt.id}`);
      const measurements = [];
      
      for (let i = 0; i < this.options.iterations; i++) {
        const measurement = await this.executeMeasuredPrompt(
          modelId, 
          prompt.text
        );
        measurements.push(measurement);
        
        // Small delay between iterations to stabilize
        await sleep(100);
      }
      
      results.push({
        prompt_id: prompt.id,
        prompt_text: prompt.text,
        measurements,
        statistics: this.calculateStatistics(measurements)
      });
    }
    
    return {
      model_id: modelId,
      timestamp: new Date().toISOString(),
      config: this.options,
      results
    };
  }
  
  async executeMeasuredPrompt(modelId, promptText) {
    const measurement = {
      success: false,
      error: null,
      ttft_ms: null,  // Time to first token
      tpot_ms: null,  // Time per output token
      total_ms: null,
      tokens_generated: 0,
      tokens_per_second: 0
    };
    
    try {
      const startTime = performance.now();
      let firstTokenTime = null;
      let tokenCount = 0;
      
      // Streaming completion to measure TTFT
      const stream = await this.client.chat.completions.create({
        model: modelId,
        messages: [{ role: 'user', content: promptText }],
        max_tokens: 200,
        temperature: 0.7,
        stream: true
      });
      
      for await (const chunk of stream) {
        if (chunk.choices[0]?.delta?.content) {
          if (firstTokenTime === null) {
            firstTokenTime = performance.now();
            measurement.ttft_ms = firstTokenTime - startTime;
          }
          tokenCount++;
        }
      }
      
      const endTime = performance.now();
      measurement.total_ms = endTime - startTime;
      measurement.tokens_generated = tokenCount;
      
      if (tokenCount > 1 && firstTokenTime) {
        // TPOT = time after first token / (tokens - 1)
        const timeAfterFirstToken = endTime - firstTokenTime;
        measurement.tpot_ms = timeAfterFirstToken / (tokenCount - 1);
        measurement.tokens_per_second = 1000 / measurement.tpot_ms;
      }
      
      measurement.success = true;
      
    } catch (error) {
      measurement.error = error.message;
      measurement.success = false;
    }
    
    return measurement;
  }
  
  calculateStatistics(measurements) {
    const successful = measurements.filter(m => m.success);
    const total = measurements.length;
    
    if (successful.length === 0) {
      return {
        success_rate: 0,
        error_rate: 1.0,
        sample_size: total
      };
    }
    
    const ttfts = successful.map(m => m.ttft_ms).sort((a, b) => a - b);
    const tpots = successful.map(m => m.tpot_ms).filter(v => v !== null).sort((a, b) => a - b);
    const totals = successful.map(m => m.total_ms).sort((a, b) => a - b);
    const throughputs = successful.map(m => m.tokens_per_second).filter(v => v > 0);
    
    return {
      success_rate: successful.length / total,
      error_rate: (total - successful.length) / total,
      sample_size: total,
      
      ttft: {
        mean: mean(ttfts),
        median: percentile(ttfts, 50),
        p95: percentile(ttfts, 95),
        p99: percentile(ttfts, 99),
        min: Math.min(...ttfts),
        max: Math.max(...ttfts)
      },
      
      tpot: tpots.length > 0 ? {
        mean: mean(tpots),
        median: percentile(tpots, 50),
        p95: percentile(tpots, 95)
      } : null,
      
      total_latency: {
        mean: mean(totals),
        median: percentile(totals, 50),
        p95: percentile(totals, 95),
        p99: percentile(totals, 99)
      },
      
      throughput: {
        mean_tps: mean(throughputs),
        median_tps: percentile(throughputs, 50)
      }
    };
  }
}

function mean(arr) {
  return arr.reduce((sum, val) => sum + val, 0) / arr.length;
}

function percentile(sortedArr, p) {
  const index = Math.ceil((sortedArr.length * p) / 100) - 1;
  return sortedArr[Math.max(0, index)];
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

This measurement infrastructure captures:

  • Time to First Token (TTFT): Critical for perceived responsiveness—users notice delays before output begins
  • Time Per Output Token (TPOT): Determines generation speed after first token—affects throughput
  • Total latency: End-to-end time—matters for batch processing and high-volume scenarios
  • Tokens per second: Overall throughput metric—useful for capacity planning
  • Statistical distributions: Mean alone masks variability—p95/p99 reveal tail latencies that impact user experience
  • Success/error rates: Stability metrics—some models timeout or crash under load

Designing Meaningful Benchmark Suites

Benchmark quality depends on prompt selection. Generic prompts don't reflect real application behavior. Design suites that mirror actual use cases:

// benchmarks/suites/default.json
{
  "name": "default",
  "description": "General-purpose benchmark covering diverse scenarios",
  "prompts": [
    {
      "id": "short-factual",
      "text": "What is the capital of France?",
      "category": "factual",
      "expected_tokens": 5
    },
    {
      "id": "medium-explanation",
      "text": "Explain how photosynthesis works in 3-4 sentences.",
      "category": "explanation",
      "expected_tokens": 80
    },
    {
      "id": "long-reasoning",
      "text": "Analyze the economic factors that led to the 2008 financial crisis. Discuss at least 5 major causes with supporting details.",
      "category": "reasoning",
      "expected_tokens": 250
    },
    {
      "id": "code-generation",
      "text": "Write a Python function that finds the longest palindrome in a string. Include docstring and example usage.",
      "category": "coding",
      "expected_tokens": 150
    },
    {
      "id": "creative-writing",
      "text": "Write a short story (3 paragraphs) about a robot learning to paint.",
      "category": "creative",
      "expected_tokens": 200
    }
  ]
}

This suite covers multiple dimensions:

  • Length variation: Short (5 tokens), medium (80), long (250)—tests models across output ranges
  • Task diversity: Factual recall, explanation, reasoning, code, creative—reveals capability breadth
  • Token predictability: Expected token counts enable throughput calculations

For production applications, create custom suites matching your actual workload:

{
  "name": "customer-support",
  "description": "Simulates actual customer support queries",
  "prompts": [
    {
      "id": "product-question",
      "text": "How do I reset my password for the customer portal?"
    },
    {
      "id": "troubleshooting",
      "text": "I'm getting error code 503 when trying to upload files. What should I do?"
    },
    {
      "id": "policy-inquiry",
      "text": "What is your refund policy for annual subscriptions?"
    }
  ]
}

Visualizing Multi-Dimensional Performance Comparisons

Raw numbers don't reveal insights—visualization makes patterns obvious. The frontend implements several comparison views:

Comparison Table shows side-by-side metrics:

// frontend/src/components/ResultsTable.jsx
export function ResultsTable({ results }) {
  return (
    
        {results.map(result => (
          
        ))}
      
ModelTTFT (ms)TPOT (ms)Throughput (tok/s)P95 LatencyError Rate
{result.model_id}{result.stats.ttft.median.toFixed(0)} (p95: {result.stats.ttft.p95.toFixed(0)}){result.stats.tpot?.median.toFixed(1) || 'N/A'}{result.stats.throughput.median_tps.toFixed(1)}{result.stats.total_latency.p95.toFixed(0)} ms0.05 ? 'error' : 'success'}> {(result.stats.error_rate * 100).toFixed(1)}%
  );
}

Latency Distribution Chart reveals performance consistency:

// Using Chart.js for visualization
export function LatencyChart({ results }) {
  const data = {
    labels: results.map(r => r.model_id),
    datasets: [
      {
        label: 'Median (p50)',
        data: results.map(r => r.stats.total_latency.median),
        backgroundColor: 'rgba(75, 192, 192, 0.5)'
      },
      {
        label: 'p95',
        data: results.map(r => r.stats.total_latency.p95),
        backgroundColor: 'rgba(255, 206, 86, 0.5)'
      },
      {
        label: 'p99',
        data: results.map(r => r.stats.total_latency.p99),
        backgroundColor: 'rgba(255, 99, 132, 0.5)'
      }
    ]
  };
  
  return (
    
  );
}

Recommendations Engine synthesizes multi-dimensional comparison:

export function generateRecommendations(results) {
  const recommendations = [];
  
  // Find fastest TTFT (best perceived responsiveness)
  const fastestTTFT = results.reduce((best, r) => 
    r.stats.ttft.median < best.stats.ttft.median ? r : best
  );
  recommendations.push({
    category: 'Fastest Response',
    model: fastestTTFT.model_id,
    reason: `Lowest median TTFT: ${fastestTTFT.stats.ttft.median.toFixed(0)}ms`
  });
  
  // Find highest throughput
  const highestThroughput = results.reduce((best, r) =>
    r.stats.throughput.median_tps > best.stats.throughput.median_tps ? r : best
  );
  recommendations.push({
    category: 'Best Throughput',
    model: highestThroughput.model_id,
    reason: `Highest tok/s: ${highestThroughput.stats.throughput.median_tps.toFixed(1)}`
  });
  
  // Find most consistent (lowest p95-p50 spread)
  const mostConsistent = results.reduce((best, r) => {
    const spread = r.stats.total_latency.p95 - r.stats.total_latency.median;
    const bestSpread = best.stats.total_latency.p95 - best.stats.total_latency.median;
    return spread < bestSpread ? r : best;
  });
  recommendations.push({
    category: 'Most Consistent',
    model: mostConsistent.model_id,
    reason: 'Lowest latency variance (p95-p50 spread)'
  });
  
  return recommendations;
}

Key Takeaways and Benchmarking Best Practices

Effective model benchmarking requires scientific methodology, comprehensive metrics, and application-specific testing. FLPerformance demonstrates that rigorous performance measurement is accessible to any development team.

Critical principles for model evaluation:

  • Test on target hardware: Results from cloud GPUs don't predict laptop performance
  • Measure multiple dimensions: TTFT, TPOT, throughput, consistency all matter
  • Use statistical rigor: Single runs mislead—capture distributions with adequate sample sizes
  • Design realistic workloads: Generic benchmarks don't predict your application's behavior
  • Include warmup iterations: Model loading and JIT compilation affect early measurements
  • Control concurrency: Real applications handle multiple requests—test at realistic loads
  • Document methodology: Reproducible results require documented procedures and configurations

The complete benchmarking platform with model management, measurement infrastructure, visualization dashboards, and comprehensive documentation is available at github.com/leestott/FLPerformance. Clone the repository and run the startup script to begin evaluating models on your hardware.

Resources and Further Reading

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories