Contents
Delve deeper with the history of Machine Learning & LLMs
Note: these are some musings on similarities between LLMs and human thought
The Hard Problem of LLMs
The title is a play on the hard problem of consciousness and how LLMs may approach being conscious. The problem is stated something like how do the material processes of the brain give rise to human consciousness. This is one of my favorite problems in philosophy and now is the first time I have seen something of a possible answer to it. Let me explain.
Embeddings and understanding
The weights of an LLM can be seen as representing concepts. They are embeddings into a multi-dimensional space. People found that subtracting the embedding vector for woman from the one for queen gives us a vector close to the one we would get if we subtract v(man) from v(king) [1]. This seems to indicate that there is a space of concepts that we can index into. Calling this understand may be a bit premature, but let's continue.
Platonic representation
You might wonder if the embeddings of one model are the same as another. No, they're probably not. But there does seem to be a relationship still. Perhaps the relationships of the vectors in one model's embedding space, are similar to the ones in a different model's embedding space? Yes, this seems more likely. The idea is called the Platonic Representation Hypothesis [2].
Are you thinking what I'm thinking?
Could we transform the embeddings from one model to another? Yes, it looks like we can. Jha et al. (2025) introduced the first method for translating text embeddings from one vector space to another without any paired data, encoders, or predefined matches. While this has security implications, it could also lead to innovations. What if we used the vector spaces from multiple models and try to derive a more complete space, or one with more accurate vectors (such that they better represent Platonic semantic meanings)?
Is word generation thinking?
To answer that question we should define what thinking is. Google Gemini said it's "Thinking is the mental process of manipulating information to form concepts, solve problems, make decisions, and understand the world."; Grok said: "cognitive process of using one's mind to consider, reason about, or manipulate ideas..."; Others give similar definitions, but they seem a tad circular to me. "mental process of manipulating ideas" and "using one's mind to consider" are about the same to me, but don't get to the underlying mechanism. I like the idea of a train of thought better. Thinking is generating words in your mind, based on the words you thought last. The thing is, there are multiple ways of thinking. The definition I just gave applies to when we are chatting with someone, or writing something like this paragraph. If someone asks us to do arithmetic, then we may be using a different part of the brain and the definition of thinking changes somewhat. But generating words (or concepts) based on previous words, is what LLMs do, so are they thinking? Yes, according to our definition, they are. But that doesn't mean they are conscious.
Are we there yet?
If defining thinking is hard, defining consciousness is even harder. That said, LLMs are not conscious in the way we are, yet. They would need to have a continuous, though not infinite, context. They would need to selectively forget things, and be able to update their weights as they generate new information. But maybe we are more machine-like than we thought.
The Art of Debugging
Debugging is a skill you hone over the years. With time you will start to recognize problems and their solutions. But there are some principles that you can keep in mind, especially when you get stuck on a difficult bug.
You may assume the input is always in a certain range or of a certain type -- verify that it is.
You may assume a library or class does what it says on the tin -- verify that it does.
For really tricky bugs it's helpful to write down the assumptions, the inputs, other system state, and to write down a plan of attack.
- Verify your assumptions
- Verify your inputs
- Break down the problem
- Keep track of your progress
- Explain the problem to someone
- Don't give up
You may assume the input is always in a certain range or of a certain type -- verify that it is.
You may assume a library or class does what it says on the tin -- verify that it does.
For really tricky bugs it's helpful to write down the assumptions, the inputs, other system state, and to write down a plan of attack.
Test Automation
When done right, test automation can save you a lot of time. Test automation is a big topic, and here I'll just cover some observations and tips that I found useful.
- Write the tests early in the development process. That way you make use of the investment longer.
- Run the tests on a schedule.
- Review the test results frequently. Ideally you set alerts for when tests fail.
- Make the test results part of your metrics.
What tools to use?
It depends on what the system under test is. For a web application, I would highly recommend Microsoft Playwright. It supports difficult use cases such as iframes and shadow DOM. At Optum we built a system where the user would launch tests from ALM (formerly HP ALM, now managed by Opentext) by clicking a toolbar button. We hooked up a script to the button that would invoke GitHub Actions workflow to run the Playwright tests. Playwright lets you take screenshots and record videos of the test runs. If a test failed, we would create a defect in ALM and attach screenshots and videos to it. We also emailed them to the user. These tests were for the Epic EMR (which now has a web version).
Software Security
Delve deeper with the Dig deeper into Security
This section is about the steps you need to take as a software engineer to ensure that the products you make are secure. If your team is just starting to implement secure coding practices, then this is what you should do.
- Scan your code for secrets
- Scan your dependencies for vulnerabilities
- Scan your code for vulnerabilities
- ≥ 9 is a critical; fix immediately / best effort
- 7 - 8.9 is a high; fix within 7 days
- 4 - 6.9 is a medium
- ≤ 3.9 is a low
Avoid writing insecure code in the first place
Make sure your team is familiar with the OWASP Top 10, which is a list of the most common security vulnerabilities affecting web applications. Incorporate security review into your PR process. Make it an opportunity to learn and improve.
Scan your code for secrets
The reason I put this first is that the impact of failing this is high. You or your developers may have committed proof of concept code with secrets in it. Or you may even have config files with secrets in them, directly in the repository. If you find secrets, you should invalidate them. If you have config files in your repo, you should switch to, ideally, using a vault. You can also use environment variables to store secrets, but this is not as secure.
Scan your dependencies for vulnerabilities
SCA -- Software Composition Analysis -- is the process of analyzing the dependencies of your code. It will use your package manager to list the dependencies and then scan them for vulnerabilities. It then continues to for vulnerabilities recursively. For example, if you use
npm
SCA will use package.json
and package-lock.json
to list the dependencies.By the way, you should commit the
To fix the vulnerability, run package-lock.json
file to your repository. You should also use specific versions of the dependencies, not just ranges.npm ls <package-name>
to see where in the tree the package is used. If it is a direct dependency, you can just update the version but if it is a transitive dependency, you may need to add an entry in the overrides
section of the package.json
file.Scan your code for vulnerabilities
SAST -- Static Application Security Testing -- is the process of analyzing your code for vulnerabilities the authors of the code added. If you use a platform like GitHub, then you will have this available right away. Scanning your code for vulnerabilities might have an extra cost. This is straightforward to handle. Your scanner will explain the vulnerability and how to fix it.
Software Development Lifecycle
Here is a typical software development lifecycle:
- What task to work on
- Projects
- Maintenance
- Security fix
- Bug fix
- Source Control
- Create a new branch in the repo
- Add Branch: <branch> to the card on the board you are working on
- Branch names should be snakecase (e.g., my-branch).
- Write code
- Write the feature / fix the bug
- Add unit tests if new feature
- Add test automation that covers the new feature or that would have caught the bug
- If you need to add libraries:
- Discuss with the senior developers
- Commit your code to a branch and check SCA and SAST for vulnerabilities
- Use of AI agents for coding is encouraged
- The same controls apply to generated code as to normal code. All code must be secure, maintainable, etc. You MUST understand the code you commit. This includes security fixes, complicated regex, and other code that an agent may be used for. Generated code must match our conventions.
- Pay specific attention to comments. In general we discourage comments in the code. Instead, write code that documents itself by use of clear variable, function, and type names. Clarification: Use comments to explain algorithms and to describe business rules. Avoid comments that say what a variable is for.
- When using an agent we encourage you to keep track of what an agent has tried, so that you can have it continue after a restart, and not repeat what was tried. This can be done by instructing the agent to write two files with things it tried: one json file and one text file. Then have include instructions in future prompts to read and use the files.
- SAST – Static Application Security Scanning
- Check GitHub Advanced Security for any code vulnerabilities.
- Build it using GitHub Actions
- Pick a test server that isn't currently used for the same part of the code
- Add Deployed to: <environment> to the card on the board you are working on
- Do developer testing
- Make sure your feature works / bug is fixed, in a lower env (TST, TST2, POC)
- Most developers will develop and test locally before deploying
- Create a PR (pull request)
- Add PR: <PR URL> to the card you are working on
- Respond to PR comments. Reviewers should follow the Code Review Checklist
- The PR must be approved by one senior engineer and one other engineer
- The PR must be approved by QA, who will leave a comment as to what was tested (if applicable) and when it is ok to merge
- Do not merge until the full team is in agreement that we will have a release
- Change management would go here, but it is left out because it is usually organization specific.
- Merge PR
- Once all conditions (see above) are right, merge the PR
- For most apps this will trigger a release
- You can also trigger the release manually in GitHub Actions (check with senior engineers)
- Production validation
- Watch the release in GitHub Actions and ensure all steps are successful
- Validate that the new feature works / that the bug is fixed, in production
This Website
This website is built with Next.js and Tailwind CSS. It is hosted on GitHub Pages. It is server-side rendered and statically generated by a GitHub Actions workflow. The code is available on GitHub at hnordberg/hnordberg.github.io. To publish, I push to the master branch. If it had been built as a collaboration, I would be using pull requests instead.